Show Data Store tiles on the Process Group page (#917)
Co-authored-by: Kevin Burnett <18027+burnettk@users.noreply.github.com>
This commit is contained in:
parent
858e8eaec4
commit
4758634c99
|
@ -305,7 +305,7 @@ paths:
|
||||||
- name: per_page
|
- name: per_page
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
description: The number of groups to show per page. Defaults to page 10.
|
description: The number of groups to show per page. Defaults to 10.
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
get:
|
get:
|
||||||
|
@ -449,7 +449,7 @@ paths:
|
||||||
- name: per_page
|
- name: per_page
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
description: The number of models to show per page. Defaults to page 10.
|
description: The number of models to show per page. Defaults to 10.
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
get:
|
get:
|
||||||
|
@ -2439,7 +2439,7 @@ paths:
|
||||||
- name: per_page
|
- name: per_page
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
description: The number of models to show per page. Defaults to page 10.
|
description: The number of models to show per page. Defaults to 10.
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
get:
|
get:
|
||||||
|
@ -2498,7 +2498,7 @@ paths:
|
||||||
- name: per_page
|
- name: per_page
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
description: The number of items to show per page. Defaults to page 10.
|
description: The number of items to show per page. Defaults to 10.
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
- name: events
|
- name: events
|
||||||
|
@ -2621,7 +2621,7 @@ paths:
|
||||||
- name: per_page
|
- name: per_page
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
description: The number of items to show per page. Defaults to page 10.
|
description: The number of items to show per page. Defaults to 10.
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
post:
|
post:
|
||||||
|
@ -2759,6 +2759,25 @@ paths:
|
||||||
# - application/json
|
# - application/json
|
||||||
|
|
||||||
/data-stores:
|
/data-stores:
|
||||||
|
parameters:
|
||||||
|
- name: process_group_identifier
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Optional parameter to filter by a single group
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: The page number to return. Defaults to page 1.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
- name: per_page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: The number of groups to show per page. Defaults to 10.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
get:
|
get:
|
||||||
operationId: spiffworkflow_backend.routes.data_store_controller.data_store_list
|
operationId: spiffworkflow_backend.routes.data_store_controller.data_store_list
|
||||||
summary: Return a list of the data store objects.
|
summary: Return a list of the data store objects.
|
||||||
|
@ -2780,6 +2799,19 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The newly created data store instance
|
description: The newly created data store instance
|
||||||
|
put:
|
||||||
|
operationId: spiffworkflow_backend.routes.data_store_controller.data_store_update
|
||||||
|
summary: Update a data store instance.
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/DataStore"
|
||||||
|
tags:
|
||||||
|
- Data Stores
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The updated data store instance
|
||||||
/data-stores/types:
|
/data-stores/types:
|
||||||
get:
|
get:
|
||||||
operationId: spiffworkflow_backend.routes.data_store_controller.data_store_types
|
operationId: spiffworkflow_backend.routes.data_store_controller.data_store_types
|
||||||
|
@ -2789,7 +2821,7 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The list of currently defined data store types
|
description: The list of currently defined data store types
|
||||||
/data-stores/{data_store_type}/{name}:
|
/data-stores/{data_store_type}/{name}/items:
|
||||||
parameters:
|
parameters:
|
||||||
- name: data_store_type
|
- name: data_store_type
|
||||||
in: path
|
in: path
|
||||||
|
@ -2823,6 +2855,34 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: A list of the data stored in the requested data store.
|
description: A list of the data stored in the requested data store.
|
||||||
|
/data-stores/{data_store_type}/{identifier}:
|
||||||
|
parameters:
|
||||||
|
- name: data_store_type
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The type of datastore, such as "typeahead"
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: identifier
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The identifier of the datastore, such as "cities"
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: process_group_identifier
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: The process group
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
get:
|
||||||
|
operationId: spiffworkflow_backend.routes.data_store_controller.data_store_show
|
||||||
|
summary: Returns a description of the data store.
|
||||||
|
tags:
|
||||||
|
- Data Stores
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The requested data store.
|
||||||
|
|
||||||
components:
|
components:
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
|
|
|
@ -3,15 +3,19 @@ from typing import Any
|
||||||
|
|
||||||
class DataStoreCRUD:
|
class DataStoreCRUD:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_instance(name: str, identifier: str, location: str, schema: dict[str, Any], description: str | None) -> None:
|
def create_instance(identifier: str, location: str) -> Any:
|
||||||
raise Exception("must implement")
|
raise Exception("must implement")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def existing_data_stores() -> list[dict[str, Any]]:
|
def existing_instance(identifier: str, location: str) -> Any:
|
||||||
raise Exception("must implement")
|
raise Exception("must implement")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def query_data_store(name: str) -> Any:
|
def existing_data_stores(process_group_identifier: str | None = None) -> list[dict[str, Any]]:
|
||||||
|
raise Exception("must implement")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_data_store_query(name: str, process_group_identifier: str | None) -> Any:
|
||||||
raise Exception("must implement")
|
raise Exception("must implement")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -33,32 +33,38 @@ class JSONDataStore(BpmnDataStoreSpecification, DataStoreCRUD): # type: ignore
|
||||||
"""JSONDataStore."""
|
"""JSONDataStore."""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_instance(name: str, identifier: str, location: str, schema: dict[str, Any], description: str | None) -> None:
|
def create_instance(identifier: str, location: str) -> Any:
|
||||||
model = JSONDataStoreModel(
|
return JSONDataStoreModel(
|
||||||
name=name,
|
|
||||||
identifier=identifier,
|
identifier=identifier,
|
||||||
location=location,
|
location=location,
|
||||||
schema=schema,
|
|
||||||
description=description or "",
|
|
||||||
data={},
|
data={},
|
||||||
)
|
)
|
||||||
db.session.add(model)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def existing_data_stores() -> list[dict[str, Any]]:
|
def existing_instance(identifier: str, location: str) -> Any:
|
||||||
|
return db.session.query(JSONDataStoreModel).filter_by(identifier=identifier, location=location).first()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def existing_data_stores(process_group_identifier: str | None = None) -> list[dict[str, Any]]:
|
||||||
data_stores = []
|
data_stores = []
|
||||||
|
|
||||||
query = db.session.query(JSONDataStoreModel.name, JSONDataStoreModel.identifier)
|
query = db.session.query(JSONDataStoreModel.name, JSONDataStoreModel.identifier)
|
||||||
|
if process_group_identifier is not None:
|
||||||
|
query = query.filter_by(location=process_group_identifier)
|
||||||
keys = query.distinct().order_by(JSONDataStoreModel.name).all() # type: ignore
|
keys = query.distinct().order_by(JSONDataStoreModel.name).all() # type: ignore
|
||||||
for key in keys:
|
for key in keys:
|
||||||
data_stores.append({"name": key[0], "type": "json", "identifier": key[1], "clz": "JSONDataStore"})
|
data_stores.append({"name": key[0], "type": "json", "id": key[1], "clz": "JSONDataStore"})
|
||||||
|
|
||||||
return data_stores
|
return data_stores
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def query_data_store(name: str) -> Any:
|
def get_data_store_query(identifier: str, process_group_identifier: str | None) -> Any:
|
||||||
return JSONDataStoreModel.query.filter_by(name=name).order_by(JSONDataStoreModel.name)
|
query = JSONDataStoreModel.query
|
||||||
|
if process_group_identifier is not None:
|
||||||
|
query = query.filter_by(identifier=identifier, location=process_group_identifier)
|
||||||
|
else:
|
||||||
|
query = query.filter_by(name=identifier)
|
||||||
|
return query.order_by(JSONDataStoreModel.name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_response_item(model: Any) -> dict[str, Any]:
|
def build_response_item(model: Any) -> dict[str, Any]:
|
||||||
|
|
|
@ -13,8 +13,12 @@ class KKVDataStore(BpmnDataStoreSpecification, DataStoreCRUD): # type: ignore
|
||||||
"""KKVDataStore."""
|
"""KKVDataStore."""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def existing_data_stores() -> list[dict[str, Any]]:
|
def existing_data_stores(process_group_identifier: str | None = None) -> list[dict[str, Any]]:
|
||||||
data_stores = []
|
data_stores: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
if process_group_identifier is not None:
|
||||||
|
# temporary until this data store gets location support
|
||||||
|
return data_stores
|
||||||
|
|
||||||
keys = (
|
keys = (
|
||||||
db.session.query(KKVDataStoreModel.top_level_key)
|
db.session.query(KKVDataStoreModel.top_level_key)
|
||||||
|
@ -23,12 +27,12 @@ class KKVDataStore(BpmnDataStoreSpecification, DataStoreCRUD): # type: ignore
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
for key in keys:
|
for key in keys:
|
||||||
data_stores.append({"name": key[0], "type": "kkv", "identifier": "", "clz": "KKVDataStore"})
|
data_stores.append({"name": key[0], "type": "kkv", "id": "", "clz": "KKVDataStore"})
|
||||||
|
|
||||||
return data_stores
|
return data_stores
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def query_data_store(name: str) -> Any:
|
def get_data_store_query(name: str, process_group_identifier: str | None) -> Any:
|
||||||
return KKVDataStoreModel.query.filter_by(top_level_key=name).order_by(
|
return KKVDataStoreModel.query.filter_by(top_level_key=name).order_by(
|
||||||
KKVDataStoreModel.top_level_key, KKVDataStoreModel.secondary_key
|
KKVDataStoreModel.top_level_key, KKVDataStoreModel.secondary_key
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,17 +14,21 @@ class TypeaheadDataStore(BpmnDataStoreSpecification, DataStoreCRUD): # type: ig
|
||||||
"""TypeaheadDataStore."""
|
"""TypeaheadDataStore."""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def existing_data_stores() -> list[dict[str, Any]]:
|
def existing_data_stores(process_group_identifier: str | None = None) -> list[dict[str, Any]]:
|
||||||
data_stores = []
|
data_stores: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
if process_group_identifier is not None:
|
||||||
|
# temporary until this data store gets location support
|
||||||
|
return data_stores
|
||||||
|
|
||||||
keys = db.session.query(TypeaheadModel.category).distinct().order_by(TypeaheadModel.category).all() # type: ignore
|
keys = db.session.query(TypeaheadModel.category).distinct().order_by(TypeaheadModel.category).all() # type: ignore
|
||||||
for key in keys:
|
for key in keys:
|
||||||
data_stores.append({"name": key[0], "type": "typeahead", "identifier": key[0], "clz": "TypeaheadDataStore"})
|
data_stores.append({"name": key[0], "type": "typeahead", "id": key[0], "clz": "TypeaheadDataStore"})
|
||||||
|
|
||||||
return data_stores
|
return data_stores
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def query_data_store(name: str) -> Any:
|
def get_data_store_query(name: str, process_group_identifier: str | None) -> Any:
|
||||||
return TypeaheadModel.query.filter_by(category=name).order_by(TypeaheadModel.category, TypeaheadModel.search_term)
|
return TypeaheadModel.query.filter_by(category=name).order_by(TypeaheadModel.category, TypeaheadModel.search_term)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -10,6 +10,7 @@ from spiffworkflow_backend.data_stores.json import JSONDataStore
|
||||||
from spiffworkflow_backend.data_stores.kkv import KKVDataStore
|
from spiffworkflow_backend.data_stores.kkv import KKVDataStore
|
||||||
from spiffworkflow_backend.data_stores.typeahead import TypeaheadDataStore
|
from spiffworkflow_backend.data_stores.typeahead import TypeaheadDataStore
|
||||||
from spiffworkflow_backend.exceptions.api_error import ApiError
|
from spiffworkflow_backend.exceptions.api_error import ApiError
|
||||||
|
from spiffworkflow_backend.models.db import db
|
||||||
|
|
||||||
DATA_STORES = {
|
DATA_STORES = {
|
||||||
"json": (JSONDataStore, "JSON Data Store"),
|
"json": (JSONDataStore, "JSON Data Store"),
|
||||||
|
@ -18,15 +19,15 @@ DATA_STORES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def data_store_list() -> flask.wrappers.Response:
|
def data_store_list(process_group_identifier: str | None = None, page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
||||||
"""Returns a list of the names of all the data stores."""
|
"""Returns a list of the names of all the data stores."""
|
||||||
data_stores = []
|
data_stores = []
|
||||||
|
|
||||||
# Right now the only data stores we support are type ahead, kkv, json
|
# Right now the only data stores we support are type ahead, kkv, json
|
||||||
|
|
||||||
data_stores.extend(JSONDataStore.existing_data_stores())
|
data_stores.extend(JSONDataStore.existing_data_stores(process_group_identifier))
|
||||||
data_stores.extend(TypeaheadDataStore.existing_data_stores())
|
data_stores.extend(TypeaheadDataStore.existing_data_stores(process_group_identifier))
|
||||||
data_stores.extend(KKVDataStore.existing_data_stores())
|
data_stores.extend(KKVDataStore.existing_data_stores(process_group_identifier))
|
||||||
|
|
||||||
return make_response(jsonify(data_stores), 200)
|
return make_response(jsonify(data_stores), 200)
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ def data_store_types() -> flask.wrappers.Response:
|
||||||
|
|
||||||
|
|
||||||
def _build_response(data_store_class: Any, name: str, page: int, per_page: int) -> flask.wrappers.Response:
|
def _build_response(data_store_class: Any, name: str, page: int, per_page: int) -> flask.wrappers.Response:
|
||||||
data_store_query = data_store_class.query_data_store(name)
|
data_store_query = data_store_class.get_data_store_query(name, None)
|
||||||
data = data_store_query.paginate(page=page, per_page=per_page, error_out=False)
|
data = data_store_query.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
results = []
|
results = []
|
||||||
for item in data.items:
|
for item in data.items:
|
||||||
|
@ -70,6 +71,14 @@ def data_store_item_list(data_store_type: str, name: str, page: int = 1, per_pag
|
||||||
|
|
||||||
|
|
||||||
def data_store_create(body: dict) -> flask.wrappers.Response:
|
def data_store_create(body: dict) -> flask.wrappers.Response:
|
||||||
|
return _data_store_upsert(body, True)
|
||||||
|
|
||||||
|
|
||||||
|
def data_store_update(body: dict) -> flask.wrappers.Response:
|
||||||
|
return _data_store_upsert(body, False)
|
||||||
|
|
||||||
|
|
||||||
|
def _data_store_upsert(body: dict, insert: bool) -> flask.wrappers.Response:
|
||||||
try:
|
try:
|
||||||
data_store_type = body["type"]
|
data_store_type = body["type"]
|
||||||
name = body["name"]
|
name = body["name"]
|
||||||
|
@ -97,6 +106,46 @@ def data_store_create(body: dict) -> flask.wrappers.Response:
|
||||||
raise ApiError("unknown_data_store", f"Unknown data store type: {data_store_type}", status_code=400)
|
raise ApiError("unknown_data_store", f"Unknown data store type: {data_store_type}", status_code=400)
|
||||||
|
|
||||||
data_store_class, _ = DATA_STORES[data_store_type]
|
data_store_class, _ = DATA_STORES[data_store_type]
|
||||||
data_store_class.create_instance(name, identifier, location, schema, description)
|
|
||||||
|
if insert:
|
||||||
|
model = data_store_class.create_instance(identifier, location)
|
||||||
|
else:
|
||||||
|
model = data_store_class.existing_instance(identifier, location)
|
||||||
|
|
||||||
|
model.name = name
|
||||||
|
model.schema = schema
|
||||||
|
model.description = description or ""
|
||||||
|
|
||||||
|
db.session.add(model)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
return make_response(jsonify({"ok": True}), 200)
|
return make_response(jsonify({"ok": True}), 200)
|
||||||
|
|
||||||
|
|
||||||
|
def data_store_show(data_store_type: str, identifier: str, process_group_identifier: str) -> flask.wrappers.Response:
|
||||||
|
"""Returns a description of a data store."""
|
||||||
|
|
||||||
|
if data_store_type not in DATA_STORES:
|
||||||
|
raise ApiError("unknown_data_store", f"Unknown data store type: {data_store_type}", status_code=400)
|
||||||
|
|
||||||
|
data_store_class, _ = DATA_STORES[data_store_type]
|
||||||
|
data_store_query = data_store_class.get_data_store_query(identifier, process_group_identifier)
|
||||||
|
result = data_store_query.first()
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
raise ApiError(
|
||||||
|
"could_not_locate_data_store",
|
||||||
|
f"Could not locate data store type: {data_store_type} for process group: {process_group_identifier}",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"name": result.name,
|
||||||
|
"location": result.location,
|
||||||
|
"type": data_store_type,
|
||||||
|
"id": result.identifier,
|
||||||
|
"schema": result.schema,
|
||||||
|
"description": result.description,
|
||||||
|
}
|
||||||
|
|
||||||
|
return make_response(jsonify(response), 200)
|
||||||
|
|
|
@ -67,8 +67,8 @@ class TestDataStores(BaseTest):
|
||||||
self.load_data_store(app, client, with_db_and_bpmn_file_cleanup, with_super_admin_user)
|
self.load_data_store(app, client, with_db_and_bpmn_file_cleanup, with_super_admin_user)
|
||||||
results = client.get("/v1.0/data-stores", headers=self.logged_in_headers(with_super_admin_user))
|
results = client.get("/v1.0/data-stores", headers=self.logged_in_headers(with_super_admin_user))
|
||||||
assert results.json == [
|
assert results.json == [
|
||||||
{"name": "albums", "type": "typeahead", "identifier": "albums", "clz": "TypeaheadDataStore"},
|
{"name": "albums", "type": "typeahead", "id": "albums", "clz": "TypeaheadDataStore"},
|
||||||
{"name": "cereals", "type": "typeahead", "identifier": "cereals", "clz": "TypeaheadDataStore"},
|
{"name": "cereals", "type": "typeahead", "id": "cereals", "clz": "TypeaheadDataStore"},
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_get_data_store_returns_paginated_results(
|
def test_get_data_store_returns_paginated_results(
|
||||||
|
@ -80,7 +80,7 @@ class TestDataStores(BaseTest):
|
||||||
) -> None:
|
) -> None:
|
||||||
self.load_data_store(app, client, with_db_and_bpmn_file_cleanup, with_super_admin_user)
|
self.load_data_store(app, client, with_db_and_bpmn_file_cleanup, with_super_admin_user)
|
||||||
response = client.get(
|
response = client.get(
|
||||||
"/v1.0/data-stores/typeahead/albums?per_page=10", headers=self.logged_in_headers(with_super_admin_user)
|
"/v1.0/data-stores/typeahead/albums/items?per_page=10", headers=self.logged_in_headers(with_super_admin_user)
|
||||||
)
|
)
|
||||||
|
|
||||||
expected_item_in_response = {
|
expected_item_in_response = {
|
||||||
|
|
|
@ -49,7 +49,11 @@ export default function DataStoreForm({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleSetDataStoreTypesCallback = (result: any) => {
|
const handleSetDataStoreTypesCallback = (result: any) => {
|
||||||
|
const dataStoreType = result.find((item: any) => {
|
||||||
|
return item.type === dataStore.type;
|
||||||
|
});
|
||||||
setDataStoreTypes(result);
|
setDataStoreTypes(result);
|
||||||
|
setSelectedDataStoreType(dataStoreType ?? null);
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
|
@ -57,7 +61,7 @@ export default function DataStoreForm({
|
||||||
successCallback: handleSetDataStoreTypesCallback,
|
successCallback: handleSetDataStoreTypesCallback,
|
||||||
httpMethod: 'GET',
|
httpMethod: 'GET',
|
||||||
});
|
});
|
||||||
}, [setDataStoreTypes]);
|
}, [dataStore, setDataStoreTypes]);
|
||||||
|
|
||||||
const navigateToDataStores = (_result: any) => {
|
const navigateToDataStores = (_result: any) => {
|
||||||
const location = dataStoreLocation();
|
const location = dataStoreLocation();
|
||||||
|
@ -99,10 +103,9 @@ export default function DataStoreForm({
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let path = '/data-stores';
|
const path = '/data-stores';
|
||||||
let httpMethod = 'POST';
|
let httpMethod = 'POST';
|
||||||
if (mode === 'edit') {
|
if (mode === 'edit') {
|
||||||
path = `/data-stores/${dataStore.id}`;
|
|
||||||
httpMethod = 'PUT';
|
httpMethod = 'PUT';
|
||||||
}
|
}
|
||||||
const postBody = {
|
const postBody = {
|
||||||
|
@ -177,26 +180,25 @@ export default function DataStoreForm({
|
||||||
/>,
|
/>,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (mode === 'new') {
|
textInputs.push(
|
||||||
textInputs.push(
|
<TextInput
|
||||||
<TextInput
|
id="data-store-identifier"
|
||||||
id="data-store-identifier"
|
name="id"
|
||||||
name="id"
|
readonly={mode === 'edit'}
|
||||||
invalidText="Identifier is required and must be all lowercase characters and hyphens."
|
invalidText="Identifier is required and must be all lowercase characters and hyphens."
|
||||||
invalid={identifierInvalid}
|
invalid={identifierInvalid}
|
||||||
labelText="Identifier*"
|
labelText="Identifier*"
|
||||||
value={dataStore.id}
|
value={dataStore.id}
|
||||||
onChange={(event: any) => {
|
onChange={(event: any) => {
|
||||||
updateDataStore({ id: event.target.value });
|
updateDataStore({ id: event.target.value });
|
||||||
// was invalid, and now valid
|
// was invalid, and now valid
|
||||||
if (identifierInvalid && hasValidIdentifier(event.target.value)) {
|
if (identifierInvalid && hasValidIdentifier(event.target.value)) {
|
||||||
setIdentifierInvalid(false);
|
setIdentifierInvalid(false);
|
||||||
}
|
}
|
||||||
setIdHasBeenUpdatedByUser(true);
|
setIdHasBeenUpdatedByUser(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
textInputs.push(
|
textInputs.push(
|
||||||
<ComboBox
|
<ComboBox
|
||||||
|
|
|
@ -51,7 +51,7 @@ export default function DataStoreListTable() {
|
||||||
}
|
}
|
||||||
const queryParamString = `per_page=${perPage}&page=${page}`;
|
const queryParamString = `per_page=${perPage}&page=${page}`;
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `/data-stores/${dataStoreType}/${dataStoreName}?${queryParamString}`,
|
path: `/data-stores/${dataStoreType}/${dataStoreName}/items?${queryParamString}`,
|
||||||
successCallback: (response: DataStoreRecords) => {
|
successCallback: (response: DataStoreRecords) => {
|
||||||
setResults(response.results);
|
setResults(response.results);
|
||||||
setPagination(response.pagination);
|
setPagination(response.pagination);
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { ReactElement, useEffect, useState } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import { ArrowRight } from '@carbon/icons-react';
|
||||||
|
import { ClickableTile } from '@carbon/react';
|
||||||
|
import HttpService from '../services/HttpService';
|
||||||
|
import { DataStore, ProcessGroup } from '../interfaces';
|
||||||
|
import { truncateString } from '../helpers';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
defaultDataStores?: DataStore[];
|
||||||
|
dataStore?: DataStore;
|
||||||
|
processGroup?: ProcessGroup;
|
||||||
|
headerElement?: ReactElement;
|
||||||
|
showNoItemsDisplayText?: boolean;
|
||||||
|
userCanCreateDataStores?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DataStoreListTiles({
|
||||||
|
defaultDataStores,
|
||||||
|
dataStore,
|
||||||
|
processGroup,
|
||||||
|
headerElement,
|
||||||
|
showNoItemsDisplayText,
|
||||||
|
userCanCreateDataStores,
|
||||||
|
}: OwnProps) {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const [dataStores, setDataStores] = useState<DataStore[] | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const setDataStoresFromResult = (result: any) => {
|
||||||
|
setDataStores(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (defaultDataStores) {
|
||||||
|
setDataStores(defaultDataStores);
|
||||||
|
} else {
|
||||||
|
let queryParams = '?per_page=1000';
|
||||||
|
if (processGroup) {
|
||||||
|
queryParams = `${queryParams}&process_group_identifier=${processGroup.id}`;
|
||||||
|
}
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/data-stores${queryParams}`,
|
||||||
|
successCallback: setDataStoresFromResult,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [searchParams, dataStore, defaultDataStores, processGroup]);
|
||||||
|
|
||||||
|
const dataStoresDisplayArea = () => {
|
||||||
|
let displayText = null;
|
||||||
|
if (dataStores && dataStores.length > 0) {
|
||||||
|
displayText = dataStores.map((row: DataStore) => {
|
||||||
|
return (
|
||||||
|
<ClickableTile
|
||||||
|
id={`data-store-tile-${row.id}`}
|
||||||
|
className="tile-data-store"
|
||||||
|
href={`/data-stores/${row.id}/edit?parentGroupId=${
|
||||||
|
processGroup?.id ?? ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="tile-data-store-content-container">
|
||||||
|
<ArrowRight />
|
||||||
|
<div className="tile-data-store-display-name">{row.name}</div>
|
||||||
|
<p className="tile-description">
|
||||||
|
{truncateString(row.description || '', 100)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ClickableTile>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if (userCanCreateDataStores) {
|
||||||
|
displayText = (
|
||||||
|
<p className="no-results-message">
|
||||||
|
There are no data stores to display. You can add one by clicking the
|
||||||
|
"Add a data store" button.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
displayText = (
|
||||||
|
<p className="no-results-message">
|
||||||
|
There are no data stores to display.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return displayText;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataStoreArea = () => {
|
||||||
|
if (dataStores && (showNoItemsDisplayText || dataStores.length > 0)) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{headerElement}
|
||||||
|
{dataStoresDisplayArea()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dataStores) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* so we can check if the data stores have loaded in cypress tests */}
|
||||||
|
<div data-qa="data-stores-loaded" className="hidden" />
|
||||||
|
{dataStoreArea()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -400,6 +400,32 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cds--tile.tile-data-store {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 12px 24px 12px 0px;
|
||||||
|
width: 320px;
|
||||||
|
height: 264px;
|
||||||
|
background: #f4f4f4;
|
||||||
|
order: 1;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-data-store-content-container {
|
||||||
|
width: 320px;
|
||||||
|
height: 264px;
|
||||||
|
padding: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-data-store-display-name {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 28px;
|
||||||
|
color: #161616;
|
||||||
|
order: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.cds--tile.tile-process-group {
|
.cds--tile.tile-process-group {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin: 12px 24px 12px 0px;
|
margin: 12px 24px 12px 0px;
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
|
import DataStoreForm from '../components/DataStoreForm';
|
||||||
|
import { DataStore, HotCrumbItem } from '../interfaces';
|
||||||
|
import { setPageTitle } from '../helpers';
|
||||||
|
import HttpService from '../services/HttpService';
|
||||||
|
|
||||||
|
export default function DataStoreEdit() {
|
||||||
|
const params = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const parentGroupId = searchParams.get('parentGroupId');
|
||||||
|
const dataStoreIdentifier = params.data_store_identifier;
|
||||||
|
const [dataStore, setDataStore] = useState<DataStore>({
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
type: '',
|
||||||
|
schema: '',
|
||||||
|
description: '',
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
setPageTitle(['Edit Data Store']);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const setDataStoreFromResult = (result: any) => {
|
||||||
|
const schema = JSON.stringify(result.schema);
|
||||||
|
setDataStore({ ...result, schema });
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryParams = `?process_group_identifier=${parentGroupId}`;
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/data-stores/json/${dataStoreIdentifier}${queryParams}`,
|
||||||
|
successCallback: setDataStoreFromResult,
|
||||||
|
});
|
||||||
|
}, [dataStoreIdentifier, parentGroupId]);
|
||||||
|
|
||||||
|
const hotCrumbs: HotCrumbItem[] = [['Process Groups', '/process-groups']];
|
||||||
|
if (parentGroupId) {
|
||||||
|
hotCrumbs.push({
|
||||||
|
entityToExplode: parentGroupId,
|
||||||
|
entityType: 'process-group-id',
|
||||||
|
linkLastItem: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProcessBreadcrumb hotCrumbs={hotCrumbs} />
|
||||||
|
<h1>Edit Data Store</h1>
|
||||||
|
<DataStoreForm
|
||||||
|
mode="edit"
|
||||||
|
dataStore={dataStore}
|
||||||
|
setDataStore={setDataStore}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Route, Routes } from 'react-router-dom';
|
||||||
import DataStoreList from './DataStoreList';
|
import DataStoreList from './DataStoreList';
|
||||||
import DataStoreNew from './DataStoreNew';
|
import DataStoreNew from './DataStoreNew';
|
||||||
|
import DataStoreEdit from './DataStoreEdit';
|
||||||
|
|
||||||
export default function DataStoreRoutes() {
|
export default function DataStoreRoutes() {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<DataStoreList />} />
|
<Route path="/" element={<DataStoreList />} />
|
||||||
|
<Route path=":data_store_identifier/edit" element={<DataStoreEdit />} />
|
||||||
<Route path="new" element={<DataStoreNew />} />
|
<Route path="new" element={<DataStoreNew />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { PermissionsToCheck, ProcessGroup } from '../interfaces';
|
||||||
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||||
import ProcessGroupListTiles from '../components/ProcessGroupListTiles';
|
import ProcessGroupListTiles from '../components/ProcessGroupListTiles';
|
||||||
|
import DataStoreListTiles from '../components/DataStoreListTiles';
|
||||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||||
import ProcessModelListTiles from '../components/ProcessModelListTiles';
|
import ProcessModelListTiles from '../components/ProcessModelListTiles';
|
||||||
import useProcessGroupFetcher from '../hooks/useProcessGroupFetcher';
|
import useProcessGroupFetcher from '../hooks/useProcessGroupFetcher';
|
||||||
|
@ -162,6 +163,17 @@ export default function ProcessGroupShow() {
|
||||||
targetUris.processGroupListPath
|
targetUris.processGroupListPath
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<DataStoreListTiles
|
||||||
|
processGroup={processGroup}
|
||||||
|
headerElement={<h2 className="clear-left">Data Stores</h2>}
|
||||||
|
showNoItemsDisplayText={showNoItemsDisplayText}
|
||||||
|
userCanCreateDataStores={ability.can(
|
||||||
|
'POST',
|
||||||
|
targetUris.dataStoreListPath
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue