From 73aed82f24955359d186010b7fc98c7fb4f65082 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Thu, 13 Jul 2023 11:24:10 -0400 Subject: [PATCH] Feature/view data stores (#388) * * Added /data-stores/ endpoint accessible by privileged users that will return a list of all data stores known to the system. * Added a /data-stores/[type]/[name] endpoint that will return a list of all data stored within a data-store. * Granted users with "elevated permissions" the right to access the data store. * Added a "Data Store" link to the navigation bar beside messages. * And a few useful tests. * Still a little front end work to do to get it all looking pretty. * Added a Data Store List component that, well, displays, you guessed it! A list of data stores and their contents. Also, Carbon's paginator doesn't care how many pages you have, it's going to build the mother of all drop down lists - you got 60,000,000 records? Showing 5 at a time? It's going to be a dropdown list that contains all numbers between 1 and 12,000,000, because that makes sense! So, yea, not doing that, cutting it off at 1000 pages - you got more pages than that, the paginator can't take you there. As you can show 100 items per page, that means you can access 100,000 items instantly. * renaming data_store_items_list => data_store_item_list --- .../src/spiffworkflow_backend/api.yml | 45 + .../routes/data_store_controller.py | 46 + .../services/authorization_service.py | 2 + .../tests/data/data_stores/data_stores.bpmn | 1861 +++++++++++++++++ .../integration/test_data_stores.py | 93 + .../unit/test_authorization_service.py | 1 + .../src/components/DataStoreList.tsx | 127 ++ .../src/components/NavigationBar.tsx | 11 + .../src/components/PaginationForTable.tsx | 15 +- .../src/hooks/UriListForPermissions.tsx | 1 + spiffworkflow-frontend/src/interfaces.ts | 10 + .../src/routes/AdminRoutes.tsx | 2 + .../src/routes/DataStorePage.tsx | 11 + 13 files changed, 2224 insertions(+), 1 deletion(-) create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/routes/data_store_controller.py create mode 100644 spiffworkflow-backend/tests/data/data_stores/data_stores.bpmn create mode 100644 spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_data_stores.py create mode 100644 spiffworkflow-frontend/src/components/DataStoreList.tsx create mode 100644 spiffworkflow-frontend/src/routes/DataStorePage.tsx diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index 716b1dee5..8db6c1d40 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -2322,6 +2322,51 @@ paths: #content: # - application/json + /data-stores: + get: + operationId: spiffworkflow_backend.routes.data_store_controller.data_store_list + summary: Return a list of the data store objects. + tags: + - Data Stores + responses: + "200": + description: The list of currently defined data store objects + /data-stores/{data_store_type}/{name}: + parameters: + - name: data_store_type + in: path + required: true + description: The type of datastore, such as "typeahead" + schema: + type: string + - name: name + in: path + required: true + description: The name of the datastore, such as "cities" + 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 page number to return. Defaults to page 1. + schema: + type: integer + get: + operationId: spiffworkflow_backend.routes.data_store_controller.data_store_item_list + summary: Returns a paginated list of the contents of a data store. + tags: + - Data Stores + responses: + "200": + description: A list of the data stored in the requested data store. + + components: securitySchemes: jwt: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/data_store_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/data_store_controller.py new file mode 100644 index 000000000..5b824a93c --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/data_store_controller.py @@ -0,0 +1,46 @@ +"""APIs for dealing with process groups, process models, and process instances.""" + +import flask.wrappers +from flask import jsonify +from flask import make_response + +from spiffworkflow_backend import db +from spiffworkflow_backend.exceptions.api_error import ApiError +from spiffworkflow_backend.models.typeahead import TypeaheadModel + + +def data_store_list() -> flask.wrappers.Response: + """Returns a list of the names of all the data stores.""" + data_stores = [] + + # Right now the only data store we support is type ahead + + for cat in db.session.query(TypeaheadModel.category).distinct(): # type: ignore + data_stores.append({"name": cat[0], "type": "typeahead"}) + + return make_response(jsonify(data_stores), 200) + + +def data_store_item_list( + data_store_type: str, name: str, page: int = 1, per_page: int = 100 +) -> flask.wrappers.Response: + """Returns a list of the items in a data store.""" + if data_store_type == "typeahead": + data_store_query = TypeaheadModel.query.filter_by(category=name) + data = data_store_query.paginate(page=page, per_page=per_page, error_out=False) + results = [] + for typeahead in data.items: + result = typeahead.result + result["search_term"] = typeahead.search_term + results.append(result) + response_json = { + "results": results, + "pagination": { + "count": len(data.items), + "total": data.total, + "pages": data.pages, + }, + } + return make_response(jsonify(response_json), 200) + else: + raise ApiError("unknown_data_store", f"Unknown data store type: {data_store_type}", status_code=400) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 7c2198d05..058c6e4de 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -558,6 +558,8 @@ class AuthorizationService: for permission in ["create", "read", "update", "delete"]: permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/process-instances/*")) permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/secrets/*")) + + permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/data-stores/*")) return permissions_to_assign @classmethod diff --git a/spiffworkflow-backend/tests/data/data_stores/data_stores.bpmn b/spiffworkflow-backend/tests/data/data_stores/data_stores.bpmn new file mode 100644 index 000000000..7b0d06a2e --- /dev/null +++ b/spiffworkflow-backend/tests/data/data_stores/data_stores.bpmn @@ -0,0 +1,1861 @@ + + + + + + Flow_0tn4ed3 + + + + Flow_0lz96a1 + + + + + + + { +"typeahead": {}, + "cities_result": [ + { + "id": 52, + "name": "Ashkāsham", + "state_id": 3901, + "state_code": "BDS", + "state_name": "Badakhshan", + "country_id": 1, + "country_code": "AF", + "country_name": "Afghanistan", + "latitude": "36.68333000", + "longitude": "71.53333000", + "wikiDataId": "Q4805192" + }, + { + "id": 68, + "name": "Fayzabad", + "state_id": 3901, + "state_code": "BDS", + "state_name": "Badakhshan", + "country_id": 1, + "country_code": "AF", + "country_name": "Afghanistan", + "latitude": "37.11664000", + "longitude": "70.58002000", + "wikiDataId": "Q156558" + }, + { + "id": 78, + "name": "Jurm", + "state_id": 3901, + "state_code": "BDS", + "state_name": "Badakhshan", + "country_id": 1, + "country_code": "AF", + "country_name": "Afghanistan", + "latitude": "36.86477000", + "longitude": "70.83421000", + "wikiDataId": "Q10308323" + } + ] +} + { + "typeahead": { + "cities": [ + { + "search_term": "Ashkāsham", + "result": { + "name": "Ashkāsham", + "country": "Afghanistan", + "state": "Badakhshan" + } + }, + { + "search_term": "Fayzabad", + "result": { + "name": "Fayzabad", + "country": "Afghanistan", + "state": "Badakhshan" + } + }, + { + "search_term": "Jurm", + "result": { + "name": "Jurm", + "country": "Afghanistan", + "state": "Badakhshan" + } + } + ], + "countries": [ + { + "result": { + "name": "Afghanistan" + }, + "search_term": "Afghanistan" + } + ], + "states": [ + { + "result": { + "country": "Afghanistan", + "name": "Badakhshan" + }, + "search_term": "Badakhshan" + } + ] + } +} + + + + Flow_0tn4ed3 + Flow_0lz96a1 + + DataStoreReference_1oronwq + + results = [ + { + "name": "100% Bran", + "manufacturer": "Nabisco", + "type": "Cold", + "calories": 70, + "protein": 4, + "fat": 1, + "sodium": 130, + "fiber": 10, + "carbo": 5, + "sugars": 6, + "potass": 280, + "vitamins": 25, + "weight": 1, + "cups": 0.33, + "rating": 68.402973 + }, + { + "name": "100% Natural Bran", + "manufacturer": "Quaker Oats", + "type": "Cold", + "calories": 120, + "protein": 3, + "fat": 5, + "sodium": 15, + "fiber": 2, + "carbo": 8, + "sugars": 8, + "potass": 135, + "vitamins": 0, + "weight": 1, + "cups": 1, + "rating": 33.983679 + }, + { + "name": "All-Bran", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 70, + "protein": 4, + "fat": 1, + "sodium": 260, + "fiber": 9, + "carbo": 7, + "sugars": 5, + "potass": 320, + "vitamins": 25, + "weight": 1, + "cups": 0.33, + "rating": 59.425505 + }, + { + "name": "All-Bran with Extra Fiber", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 50, + "protein": 4, + "fat": 0, + "sodium": 140, + "fiber": 14, + "carbo": 8, + "sugars": 0, + "potass": 330, + "vitamins": 25, + "weight": 1, + "cups": 0.5, + "rating": 93.704912 + }, + { + "name": "Almond Delight", + "manufacturer": "Ralston Purina", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 2, + "sodium": 200, + "fiber": 1, + "carbo": 14, + "sugars": 8, + "potass": -1, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 34.384843 + }, + { + "name": "Apple Cinnamon Cheerios", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 2, + "sodium": 180, + "fiber": 1.5, + "carbo": 10.5, + "sugars": 10, + "potass": 70, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 29.509541 + }, + { + "name": "Apple Jacks", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 0, + "sodium": 125, + "fiber": 1, + "carbo": 11, + "sugars": 14, + "potass": 30, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 33.174094 + }, + { + "name": "Basic 4", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 130, + "protein": 3, + "fat": 2, + "sodium": 210, + "fiber": 2, + "carbo": 18, + "sugars": 8, + "potass": 100, + "vitamins": 25, + "weight": 1.33, + "cups": 0.75, + "rating": 37.038562 + }, + { + "name": "Bran Chex", + "manufacturer": "Ralston Purina", + "type": "Cold", + "calories": 90, + "protein": 2, + "fat": 1, + "sodium": 200, + "fiber": 4, + "carbo": 15, + "sugars": 6, + "potass": 125, + "vitamins": 25, + "weight": 1, + "cups": 0.67, + "rating": 49.120253 + }, + { + "name": "Bran Flakes", + "manufacturer": "Post", + "type": "Cold", + "calories": 90, + "protein": 3, + "fat": 0, + "sodium": 210, + "fiber": 5, + "carbo": 13, + "sugars": 5, + "potass": 190, + "vitamins": 25, + "weight": 1, + "cups": 0.67, + "rating": 53.313813 + }, + { + "name": "Cap'n'Crunch", + "manufacturer": "Quaker Oats", + "type": "Cold", + "calories": 120, + "protein": 1, + "fat": 2, + "sodium": 220, + "fiber": 0, + "carbo": 12, + "sugars": 12, + "potass": 35, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 18.042851 + }, + { + "name": "Cheerios", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 6, + "fat": 2, + "sodium": 290, + "fiber": 2, + "carbo": 17, + "sugars": 1, + "potass": 105, + "vitamins": 25, + "weight": 1, + "cups": 1.25, + "rating": 50.764999 + }, + { + "name": "Cinnamon Toast Crunch", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 120, + "protein": 1, + "fat": 3, + "sodium": 210, + "fiber": 0, + "carbo": 13, + "sugars": 9, + "potass": 45, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 19.823573 + }, + { + "name": "Clusters", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 3, + "fat": 2, + "sodium": 140, + "fiber": 2, + "carbo": 13, + "sugars": 7, + "potass": 105, + "vitamins": 25, + "weight": 1, + "cups": 0.5, + "rating": 40.400208 + }, + { + "name": "Cocoa Puffs", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 1, + "fat": 1, + "sodium": 180, + "fiber": 0, + "carbo": 12, + "sugars": 13, + "potass": 55, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 22.736446 + }, + { + "name": "Corn Chex", + "manufacturer": "Ralston Purina", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 0, + "sodium": 280, + "fiber": 0, + "carbo": 22, + "sugars": 3, + "potass": 25, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 41.445019 + }, + { + "name": "Corn Flakes", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 100, + "protein": 2, + "fat": 0, + "sodium": 290, + "fiber": 1, + "carbo": 21, + "sugars": 2, + "potass": 35, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 45.863324 + }, + { + "name": "Corn Pops", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 110, + "protein": 1, + "fat": 0, + "sodium": 90, + "fiber": 1, + "carbo": 13, + "sugars": 12, + "potass": 20, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 35.782791 + }, + { + "name": "Count Chocula", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 1, + "fat": 1, + "sodium": 180, + "fiber": 0, + "carbo": 12, + "sugars": 13, + "potass": 65, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 22.396513 + }, + { + "name": "Cracklin' Oat Bran", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 110, + "protein": 3, + "fat": 3, + "sodium": 140, + "fiber": 4, + "carbo": 10, + "sugars": 7, + "potass": 160, + "vitamins": 25, + "weight": 1, + "cups": 0.5, + "rating": 40.448772 + }, + { + "name": "Cream of Wheat (Quick)", + "manufacturer": "Nabisco", + "type": "Hot", + "calories": 100, + "protein": 3, + "fat": 0, + "sodium": 80, + "fiber": 1, + "carbo": 21, + "sugars": 0, + "potass": -1, + "vitamins": 0, + "weight": 1, + "cups": 1, + "rating": 64.533816 + }, + { + "name": "Crispix", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 0, + "sodium": 220, + "fiber": 1, + "carbo": 21, + "sugars": 3, + "potass": 30, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 46.895644 + }, + { + "name": "Crispy Wheat & Raisins", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 100, + "protein": 2, + "fat": 1, + "sodium": 140, + "fiber": 2, + "carbo": 11, + "sugars": 10, + "potass": 120, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 36.176196 + }, + { + "name": "Double Chex", + "manufacturer": "Ralston Purina", + "type": "Cold", + "calories": 100, + "protein": 2, + "fat": 0, + "sodium": 190, + "fiber": 1, + "carbo": 18, + "sugars": 5, + "potass": 80, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 44.330856 + }, + { + "name": "Froot Loops", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 1, + "sodium": 125, + "fiber": 1, + "carbo": 11, + "sugars": 13, + "potass": 30, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 32.207582 + }, + { + "name": "Frosted Flakes", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 110, + "protein": 1, + "fat": 0, + "sodium": 200, + "fiber": 1, + "carbo": 14, + "sugars": 11, + "potass": 25, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 31.435973 + }, + { + "name": "Frosted Mini-Wheats", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 100, + "protein": 3, + "fat": 0, + "sodium": 0, + "fiber": 3, + "carbo": 14, + "sugars": 7, + "potass": 100, + "vitamins": 25, + "weight": 1, + "cups": 0.8, + "rating": 58.345141 + }, + { + "name": "Fruit & Fibre Dates; Walnuts; and Oats", + "manufacturer": "Post", + "type": "Cold", + "calories": 120, + "protein": 3, + "fat": 2, + "sodium": 160, + "fiber": 5, + "carbo": 12, + "sugars": 10, + "potass": 200, + "vitamins": 25, + "weight": 1.25, + "cups": 0.67, + "rating": 40.917047 + }, + { + "name": "Fruitful Bran", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 120, + "protein": 3, + "fat": 0, + "sodium": 240, + "fiber": 5, + "carbo": 14, + "sugars": 12, + "potass": 190, + "vitamins": 25, + "weight": 1.33, + "cups": 0.67, + "rating": 41.015492 + }, + { + "name": "Fruity Pebbles", + "manufacturer": "Post", + "type": "Cold", + "calories": 110, + "protein": 1, + "fat": 1, + "sodium": 135, + "fiber": 0, + "carbo": 13, + "sugars": 12, + "potass": 25, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 28.025765 + }, + { + "name": "Golden Crisp", + "manufacturer": "Post", + "type": "Cold", + "calories": 100, + "protein": 2, + "fat": 0, + "sodium": 45, + "fiber": 0, + "carbo": 11, + "sugars": 15, + "potass": 40, + "vitamins": 25, + "weight": 1, + "cups": 0.88, + "rating": 35.252444 + }, + { + "name": "Golden Grahams", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 1, + "fat": 1, + "sodium": 280, + "fiber": 0, + "carbo": 15, + "sugars": 9, + "potass": 45, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 23.804043 + }, + { + "name": "Grape Nuts Flakes", + "manufacturer": "Post", + "type": "Cold", + "calories": 100, + "protein": 3, + "fat": 1, + "sodium": 140, + "fiber": 3, + "carbo": 15, + "sugars": 5, + "potass": 85, + "vitamins": 25, + "weight": 1, + "cups": 0.88, + "rating": 52.076897 + }, + { + "name": "Grape-Nuts", + "manufacturer": "Post", + "type": "Cold", + "calories": 110, + "protein": 3, + "fat": 0, + "sodium": 170, + "fiber": 3, + "carbo": 17, + "sugars": 3, + "potass": 90, + "vitamins": 25, + "weight": 1, + "cups": 0.25, + "rating": 53.371007 + }, + { + "name": "Great Grains Pecan", + "manufacturer": "Post", + "type": "Cold", + "calories": 120, + "protein": 3, + "fat": 3, + "sodium": 75, + "fiber": 3, + "carbo": 13, + "sugars": 4, + "potass": 100, + "vitamins": 25, + "weight": 1, + "cups": 0.33, + "rating": 45.811716 + }, + { + "name": "Honey Graham Ohs", + "manufacturer": "Quaker Oats", + "type": "Cold", + "calories": 120, + "protein": 1, + "fat": 2, + "sodium": 220, + "fiber": 1, + "carbo": 12, + "sugars": 11, + "potass": 45, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 21.871292 + }, + { + "name": "Honey Nut Cheerios", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 3, + "fat": 1, + "sodium": 250, + "fiber": 1.5, + "carbo": 11.5, + "sugars": 10, + "potass": 90, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 31.072217 + }, + { + "name": "Honey-comb", + "manufacturer": "Post", + "type": "Cold", + "calories": 110, + "protein": 1, + "fat": 0, + "sodium": 180, + "fiber": 0, + "carbo": 14, + "sugars": 11, + "potass": 35, + "vitamins": 25, + "weight": 1, + "cups": 1.33, + "rating": 28.742414 + }, + { + "name": "Just Right Crunchy Nuggets", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 1, + "sodium": 170, + "fiber": 1, + "carbo": 17, + "sugars": 6, + "potass": 60, + "vitamins": 100, + "weight": 1, + "cups": 1, + "rating": 36.523683 + }, + { + "name": "Just Right Fruit & Nut", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 140, + "protein": 3, + "fat": 1, + "sodium": 170, + "fiber": 2, + "carbo": 20, + "sugars": 9, + "potass": 95, + "vitamins": 100, + "weight": 1.3, + "cups": 0.75, + "rating": 36.471512 + }, + { + "name": "Kix", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 1, + "sodium": 260, + "fiber": 0, + "carbo": 21, + "sugars": 3, + "potass": 40, + "vitamins": 25, + "weight": 1, + "cups": 1.5, + "rating": 39.241114 + }, + { + "name": "Life", + "manufacturer": "Quaker Oats", + "type": "Cold", + "calories": 100, + "protein": 4, + "fat": 2, + "sodium": 150, + "fiber": 2, + "carbo": 12, + "sugars": 6, + "potass": 95, + "vitamins": 25, + "weight": 1, + "cups": 0.67, + "rating": 45.328074 + }, + { + "name": "Lucky Charms", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 1, + "sodium": 180, + "fiber": 0, + "carbo": 12, + "sugars": 12, + "potass": 55, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 26.734515 + }, + { + "name": "Maypo", + "manufacturer": "American Home Food Products", + "type": "Hot", + "calories": 100, + "protein": 4, + "fat": 1, + "sodium": 0, + "fiber": 0, + "carbo": 16, + "sugars": 3, + "potass": 95, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 54.850917 + }, + { + "name": "Muesli Raisins; Dates; & Almonds", + "manufacturer": "Ralston Purina", + "type": "Cold", + "calories": 150, + "protein": 4, + "fat": 3, + "sodium": 95, + "fiber": 3, + "carbo": 16, + "sugars": 11, + "potass": 170, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 37.136863 + }, + { + "name": "Muesli Raisins; Peaches; & Pecans", + "manufacturer": "Ralston Purina", + "type": "Cold", + "calories": 150, + "protein": 4, + "fat": 3, + "sodium": 150, + "fiber": 3, + "carbo": 16, + "sugars": 11, + "potass": 170, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 34.139765 + }, + { + "name": "Mueslix Crispy Blend", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 160, + "protein": 3, + "fat": 2, + "sodium": 150, + "fiber": 3, + "carbo": 17, + "sugars": 13, + "potass": 160, + "vitamins": 25, + "weight": 1.5, + "cups": 0.67, + "rating": 30.313351 + }, + { + "name": "Multi-Grain Cheerios", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 100, + "protein": 2, + "fat": 1, + "sodium": 220, + "fiber": 2, + "carbo": 15, + "sugars": 6, + "potass": 90, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 40.105965 + }, + { + "name": "Nut&Honey Crunch", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 120, + "protein": 2, + "fat": 1, + "sodium": 190, + "fiber": 0, + "carbo": 15, + "sugars": 9, + "potass": 40, + "vitamins": 25, + "weight": 1, + "cups": 0.67, + "rating": 29.924285 + }, + { + "name": "Nutri-Grain Almond-Raisin", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 140, + "protein": 3, + "fat": 2, + "sodium": 220, + "fiber": 3, + "carbo": 21, + "sugars": 7, + "potass": 130, + "vitamins": 25, + "weight": 1.33, + "cups": 0.67, + "rating": 40.69232 + }, + { + "name": "Nutri-grain Wheat", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 90, + "protein": 3, + "fat": 0, + "sodium": 170, + "fiber": 3, + "carbo": 18, + "sugars": 2, + "potass": 90, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 59.642837 + }, + { + "name": "Oatmeal Raisin Crisp", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 130, + "protein": 3, + "fat": 2, + "sodium": 170, + "fiber": 1.5, + "carbo": 13.5, + "sugars": 10, + "potass": 120, + "vitamins": 25, + "weight": 1.25, + "cups": 0.5, + "rating": 30.450843 + }, + { + "name": "Post Nat. Raisin Bran", + "manufacturer": "Post", + "type": "Cold", + "calories": 120, + "protein": 3, + "fat": 1, + "sodium": 200, + "fiber": 6, + "carbo": 11, + "sugars": 14, + "potass": 260, + "vitamins": 25, + "weight": 1.33, + "cups": 0.67, + "rating": 37.840594 + }, + { + "name": "Product 19", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 100, + "protein": 3, + "fat": 0, + "sodium": 320, + "fiber": 1, + "carbo": 20, + "sugars": 3, + "potass": 45, + "vitamins": 100, + "weight": 1, + "cups": 1, + "rating": 41.50354 + }, + { + "name": "Puffed Rice", + "manufacturer": "Quaker Oats", + "type": "Cold", + "calories": 50, + "protein": 1, + "fat": 0, + "sodium": 0, + "fiber": 0, + "carbo": 13, + "sugars": 0, + "potass": 15, + "vitamins": 0, + "weight": 0.5, + "cups": 1, + "rating": 60.756112 + }, + { + "name": "Puffed Wheat", + "manufacturer": "Quaker Oats", + "type": "Cold", + "calories": 50, + "protein": 2, + "fat": 0, + "sodium": 0, + "fiber": 1, + "carbo": 10, + "sugars": 0, + "potass": 50, + "vitamins": 0, + "weight": 0.5, + "cups": 1, + "rating": 63.005645 + }, + { + "name": "Quaker Oat Squares", + "manufacturer": "Quaker Oats", + "type": "Cold", + "calories": 100, + "protein": 4, + "fat": 1, + "sodium": 135, + "fiber": 2, + "carbo": 14, + "sugars": 6, + "potass": 110, + "vitamins": 25, + "weight": 1, + "cups": 0.5, + "rating": 49.511874 + }, + { + "name": "Quaker Oatmeal", + "manufacturer": "Quaker Oats", + "type": "Hot", + "calories": 100, + "protein": 5, + "fat": 2, + "sodium": 0, + "fiber": 2.7, + "carbo": -1, + "sugars": -1, + "potass": 110, + "vitamins": 0, + "weight": 1, + "cups": 0.67, + "rating": 50.828392 + }, + { + "name": "Raisin Bran", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 120, + "protein": 3, + "fat": 1, + "sodium": 210, + "fiber": 5, + "carbo": 14, + "sugars": 12, + "potass": 240, + "vitamins": 25, + "weight": 1.33, + "cups": 0.75, + "rating": 39.259197 + }, + { + "name": "Raisin Nut Bran", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 100, + "protein": 3, + "fat": 2, + "sodium": 140, + "fiber": 2.5, + "carbo": 10.5, + "sugars": 8, + "potass": 140, + "vitamins": 25, + "weight": 1, + "cups": 0.5, + "rating": 39.7034 + }, + { + "name": "Raisin Squares", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 90, + "protein": 2, + "fat": 0, + "sodium": 0, + "fiber": 2, + "carbo": 15, + "sugars": 6, + "potass": 110, + "vitamins": 25, + "weight": 1, + "cups": 0.5, + "rating": 55.333142 + }, + { + "name": "Rice Chex", + "manufacturer": "Ralston Purina", + "type": "Cold", + "calories": 110, + "protein": 1, + "fat": 0, + "sodium": 240, + "fiber": 0, + "carbo": 23, + "sugars": 2, + "potass": 30, + "vitamins": 25, + "weight": 1, + "cups": 1.13, + "rating": 41.998933 + }, + { + "name": "Rice Krispies", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 0, + "sodium": 290, + "fiber": 0, + "carbo": 22, + "sugars": 3, + "potass": 35, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 40.560159 + }, + { + "name": "Shredded Wheat", + "manufacturer": "Nabisco", + "type": "Cold", + "calories": 80, + "protein": 2, + "fat": 0, + "sodium": 0, + "fiber": 3, + "carbo": 16, + "sugars": 0, + "potass": 95, + "vitamins": 0, + "weight": 0.83, + "cups": 1, + "rating": 68.235885 + }, + { + "name": "Shredded Wheat 'n'Bran", + "manufacturer": "Nabisco", + "type": "Cold", + "calories": 90, + "protein": 3, + "fat": 0, + "sodium": 0, + "fiber": 4, + "carbo": 19, + "sugars": 0, + "potass": 140, + "vitamins": 0, + "weight": 1, + "cups": 0.67, + "rating": 74.472949 + }, + { + "name": "Shredded Wheat spoon size", + "manufacturer": "Nabisco", + "type": "Cold", + "calories": 90, + "protein": 3, + "fat": 0, + "sodium": 0, + "fiber": 3, + "carbo": 20, + "sugars": 0, + "potass": 120, + "vitamins": 0, + "weight": 1, + "cups": 0.67, + "rating": 72.801787 + }, + { + "name": "Smacks", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 1, + "sodium": 70, + "fiber": 1, + "carbo": 9, + "sugars": 15, + "potass": 40, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 31.230054 + }, + { + "name": "Special K", + "manufacturer": "Kelloggs", + "type": "Cold", + "calories": 110, + "protein": 6, + "fat": 0, + "sodium": 230, + "fiber": 1, + "carbo": 16, + "sugars": 3, + "potass": 55, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 53.131324 + }, + { + "name": "Strawberry Fruit Wheats", + "manufacturer": "Nabisco", + "type": "Cold", + "calories": 90, + "protein": 2, + "fat": 0, + "sodium": 15, + "fiber": 3, + "carbo": 15, + "sugars": 5, + "potass": 90, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 59.363993 + }, + { + "name": "Total Corn Flakes", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 1, + "sodium": 200, + "fiber": 0, + "carbo": 21, + "sugars": 3, + "potass": 35, + "vitamins": 100, + "weight": 1, + "cups": 1, + "rating": 38.839746 + }, + { + "name": "Total Raisin Bran", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 140, + "protein": 3, + "fat": 1, + "sodium": 190, + "fiber": 4, + "carbo": 15, + "sugars": 14, + "potass": 230, + "vitamins": 100, + "weight": 1.5, + "cups": 1, + "rating": 28.592785 + }, + { + "name": "Total Whole Grain", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 100, + "protein": 3, + "fat": 1, + "sodium": 200, + "fiber": 3, + "carbo": 16, + "sugars": 3, + "potass": 110, + "vitamins": 100, + "weight": 1, + "cups": 1, + "rating": 46.658844 + }, + { + "name": "Triples", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 1, + "sodium": 250, + "fiber": 0, + "carbo": 21, + "sugars": 3, + "potass": 60, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 39.106174 + }, + { + "name": "Trix", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 1, + "fat": 1, + "sodium": 140, + "fiber": 0, + "carbo": 13, + "sugars": 12, + "potass": 25, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 27.753301 + }, + { + "name": "Wheat Chex", + "manufacturer": "Ralston Purina", + "type": "Cold", + "calories": 100, + "protein": 3, + "fat": 1, + "sodium": 230, + "fiber": 3, + "carbo": 17, + "sugars": 3, + "potass": 115, + "vitamins": 25, + "weight": 1, + "cups": 0.67, + "rating": 49.787445 + }, + { + "name": "Wheaties", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 100, + "protein": 3, + "fat": 1, + "sodium": 200, + "fiber": 3, + "carbo": 17, + "sugars": 3, + "potass": 110, + "vitamins": 25, + "weight": 1, + "cups": 1, + "rating": 51.592193 + }, + { + "name": "Wheaties Honey Gold", + "manufacturer": "General Mills", + "type": "Cold", + "calories": 110, + "protein": 2, + "fat": 1, + "sodium": 200, + "fiber": 1, + "carbo": 16, + "sugars": 8, + "potass": 60, + "vitamins": 25, + "weight": 1, + "cups": 0.75, + "rating": 36.187559 + } +] + +raw_albums = [ + { + "album": "Mama Said Knock You Out", + "artist": "LL Cool J", + "year": 1990 + }, + { + "album": "Nowhere", + "artist": "Ride", + "year": 1990 + }, + { + "album": "People's Instinctive Paths", + "artist": "A Tribe Called Quest", + "year": 1990 + }, + { + "album": "Pills 'n' Thrills 'n' Bellyaches", + "artist": "Happy Mondays", + "year": 1990 + }, + { + "album": "Ragged Glory", + "artist": "Neil Young", + "year": 1990 + }, + { + "album": "Repeater", + "artist": "Fugazi", + "year": 1990 + }, + { + "album": "Ritual De Lo Habitual", + "artist": "Jane's Addiction", + "year": 1990 + }, + { + "album": "Rust In Peace", + "artist": "Megadeth", + "year": 1990 + }, + { + "album": "Sex Packets", + "artist": "Digital Underground", + "year": 1990 + }, + { + "album": "Shake Your Money Maker", + "artist": "The Black Crowes", + "year": 1990 + }, + { + "album": "The La's", + "artist": "The La's", + "year": 1990 + }, + { + "album": "The White Room", + "artist": "KLF", + "year": 1990 + }, + { + "album": "Violator", + "artist": "Depeche Mode", + "year": 1990 + }, + { + "album": "World Clique", + "artist": "Deee-Lite", + "year": 1990 + }, + { + "album": "Achtung Baby", + "artist": "U2", + "year": 1991 + }, + { + "album": "Apocalypse '91 … The Enemy Strikes Black", + "artist": "Public Enemy", + "year": 1991 + }, + { + "album": "Arise", + "artist": "Sepultura", + "year": 1991 + }, + { + "album": "Bandwagonesque", + "artist": "Teenage Fanclub", + "year": 1991 + }, + { + "album": "Blood Sugar Sex Magik", + "artist": "Red Hot Chili Peppers", + "year": 1991 + }, + { + "album": "Blue Lines", + "artist": "Massive Attack", + "year": 1991 + }, + { + "album": "Cypress Hill", + "artist": "Cypress Hill", + "year": 1991 + }, + { + "album": "Every Good Boy Deserves Fudge", + "artist": "Mudhoney", + "year": 1991 + }, + { + "album": "Foxbase Alpha", + "artist": "Saint Etienne", + "year": 1991 + }, + { + "album": "Haut de Gamme–Koweit Rive Gauche", + "artist": "Koffi Olomide", + "year": 1991 + }, + { + "album": "Loveless", + "artist": "My Bloody Valentine", + "year": 1991 + }, + { + "album": "Metallica", + "artist": "Metallica", + "year": 1991 + }, + { + "album": "Nevermind", + "artist": "Nirvana", + "year": 1991 + }, + { + "album": "O.G. Original Gangster", + "artist": "Ice-T", + "year": 1991 + }, + { + "album": "Peggy Suicide", + "artist": "Julian Cope", + "year": 1991 + }, + { + "album": "Rising Above Bedlam", + "artist": "Jah Wobble And the Invaders of the Heart", + "year": 1991 + }, + { + "album": "Screamadelica", + "artist": "Primal Scream", + "year": 1991 + }, + { + "album": "Spiderland", + "artist": "Slint", + "year": 1991 + }, + { + "album": "Step in the Arena", + "artist": "Gang Starr", + "year": 1991 + }, + { + "album": "The Low End Theory", + "artist": "A Tribe Called Quest", + "year": 1991 + }, + { + "album": "Woodface", + "artist": "Crowded House", + "year": 1991 + }, + { + "album": "A Vulgar Display Of Power", + "artist": "Pantera", + "year": 1992 + }, + { + "album": "Automatic for the People", + "artist": "REM", + "year": 1992 + }, + { + "album": "Bizarre Ride II The Pharcyde", + "artist": "The Pharcyde", + "year": 1992 + }, + { + "album": "Bone Machine", + "artist": "Tom Waits", + "year": 1992 + }, + { + "album": "Connected", + "artist": "Stereo MC's", + "year": 1992 + }, + { + "album": "Copper Blue", + "artist": "Sugar", + "year": 1992 + }, + { + "album": "Devotional Songs", + "artist": "Nusrat Fateh Ali Khan & Party", + "year": 1992 + }, + { + "album": "Dirt", + "artist": "Alice in Chains", + "year": 1992 + }, + { + "album": "Dirty", + "artist": "Sonic Youth", + "year": 1992 + }, + { + "album": "Dry", + "artist": "P.J. Harvey", + "year": 1992 + }, + { + "album": "Henry's Dream", + "artist": "Nick Cave & the Bad Seeds", + "year": 1992 + }, + { + "album": "Hiphoprisy Is The Greatest Luxury", + "artist": "Disposable Heroes of Hiphoprisy", + "year": 1992 + }, + { + "album": "Ingénue", + "artist": "k.d. lang", + "year": 1992 + }, + { + "album": "It's A Shame About Ray", + "artist": "The Lemonheads", + "year": 1992 + }, + { + "album": "Lam Toro", + "artist": "Baaba Maal", + "year": 1992 + }, + { + "album": "Lazer Guided Melodies", + "artist": "Spiritualized", + "year": 1992 + }, + { + "album": "Little Earthquakes", + "artist": "Tori Amos", + "year": 1992 + }, + { + "album": "Psalm 69", + "artist": "Ministry", + "year": 1992 + }, + { + "album": "Selected Ambient Works '85–'92", + "artist": "Aphex Twin", + "year": 1992 + }, + { + "album": "Ten", + "artist": "Pearl Jam", + "year": 1992 + }, + { + "album": "Three Years, Five Months, And Two Days In The Life Of...", + "artist": "Arrested Development", + "year": 1992 + }, + { + "album": "Your Arsenal", + "artist": "Morrissey", + "year": 1992 + }, + { + "album": "Aimee Mann", + "artist": "Whatever", + "year": 1993 + }, + { + "album": "Bubble & Scrape", + "artist": "Sebadoh", + "year": 1993 + }, + { + "album": "Debut", + "artist": "Björk", + "year": 1993 + }, + { + "album": "Doggy Style", + "artist": "Snoop Doggy Dogg", + "year": 1993 + }, + { + "album": "Emergency on Planet Earth", + "artist": "Jamiroquai", + "year": 1993 + }, + { + "album": "Enter The Wu-Tang", + "artist": "Wu-Tang Clan", + "year": 1993 + }, + { + "album": "Fuzzy", + "artist": "Grant Lee Buffalo", + "year": 1993 + }, + { + "album": "Gentleman", + "artist": "The Afghan Whigs", + "year": 1993 + }, + { + "album": "Giant Steps", + "artist": "The Boo Radleys", + "year": 1993 + }, + { + "album": "In Utero", + "artist": "Nirvana", + "year": 1993 + }, + { + "album": "Modern Life is Rubbish", + "artist": "Blur", + "year": 1993 + }, + { + "album": "New Wave", + "artist": "Auteurs", + "year": 1993 + }, + { + "album": "Orbital II", + "artist": "Orbital", + "year": 1993 + }, + { + "album": "Qui Seme Le Vent Recolte Le Tempo", + "artist": "MC Solar", + "year": 1993 + }, + { + "album": "Rage Against The Machine", + "artist": "Rage Against The Machine", + "year": 1993 + }, + { + "album": "Rid Of Me", + "artist": "P.J. Harvey", + "year": 1993 + }, + { + "album": "Siamese Dream", + "artist": "Smashing Pumpkins", + "year": 1993 + }, + { + "album": "Slanted And Enchanted", + "artist": "Pavement", + "year": 1993 + }, + { + "album": "Strange Cargo III", + "artist": "William Orbit", + "year": 1993 + } +] + + +cereals = [] +for result in results: + cereals.append({"search_term": result["name"], "result": result}) + +albums = [] +for a in raw_albums: + albums.append({"search_term": a["album"], "result": a}) + +typeahead = {"cereals": cereals, "albums": albums} + +del(cereals) +del(results) +del(raw_albums) +del(albums) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_data_stores.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_data_stores.py new file mode 100644 index 000000000..57216f480 --- /dev/null +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_data_stores.py @@ -0,0 +1,93 @@ +from flask import Flask +from flask.testing import FlaskClient +from spiffworkflow_backend.models.typeahead import TypeaheadModel +from spiffworkflow_backend.models.user import UserModel + +from tests.spiffworkflow_backend.helpers.base_test import BaseTest + + +class TestDataStores(BaseTest): + def load_data_store( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + """ + Populate a datastore with some mock data using a BPMN process that will load information + using the typeahead data store. This should add 77 entries to the typeahead table. + """ + process_group_id = "data_stores" + process_model_id = "cereals_data_store" + bpmn_file_location = "data_stores" + process_model = self.create_group_and_model_with_bpmn( + client, + with_super_admin_user, + process_group_id=process_group_id, + process_model_id=process_model_id, + bpmn_file_location=bpmn_file_location, + ) + + headers = self.logged_in_headers(with_super_admin_user) + response = self.create_process_instance_from_process_model_id_with_api(client, process_model.id, headers) + assert response.json is not None + process_instance_id = response.json["id"] + + client.post( + f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model.id)}/{process_instance_id}/run", + headers=self.logged_in_headers(with_super_admin_user), + ) + + def test_create_data_store_populates_db( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + """Assure that when we run this workflow it will autofill the typeahead data store.""" + self.load_data_store(app, client, with_db_and_bpmn_file_cleanup, with_super_admin_user) + typeaheads = TypeaheadModel.query.all() + assert len(typeaheads) == 153 + + def test_get_list_of_data_stores( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + """ + It should be possible to get a list of the data store categories that are available. + """ + results = client.get("/v1.0/data-stores", headers=self.logged_in_headers(with_super_admin_user)) + assert results.json == [] + + 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)) + assert results.json == [{"name": "albums", "type": "typeahead"}, {"name": "cereals", "type": "typeahead"}] + + def test_get_data_store_returns_paginated_results( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + self.load_data_store(app, client, with_db_and_bpmn_file_cleanup, with_super_admin_user) + response = client.get( + "/v1.0/data-stores/typeahead/albums?per_page=10", headers=self.logged_in_headers(with_super_admin_user) + ) + + assert response.json is not None + assert len(response.json["results"]) == 10 + assert response.json["pagination"]["count"] == 10 + assert response.json["pagination"]["total"] == 76 + assert response.json["pagination"]["pages"] == 8 + assert response.json["results"][0] == { + "search_term": "Mama Said Knock You Out", + "year": 1990, + "album": "Mama Said Knock You Out", + "artist": "LL Cool J", + } diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index 60cef8b2e..b6070c616 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -315,6 +315,7 @@ class TestAuthorizationService(BaseTest): [ ("/authentications", "read"), ("/can-run-privileged-script/*", "create"), + ("/data-stores/*", "read"), ("/debug/*", "create"), ("/event-error-details/*", "read"), ("/logs/*", "read"), diff --git a/spiffworkflow-frontend/src/components/DataStoreList.tsx b/spiffworkflow-frontend/src/components/DataStoreList.tsx new file mode 100644 index 000000000..990a9021c --- /dev/null +++ b/spiffworkflow-frontend/src/components/DataStoreList.tsx @@ -0,0 +1,127 @@ +import { useEffect, useState } from 'react'; +import { + Dropdown, + Table, + TableHead, + TableHeader, + TableRow, +} from '@carbon/react'; +import { TableBody, TableCell } from '@mui/material'; +import { useSearchParams } from 'react-router-dom'; +import HttpService from '../services/HttpService'; +import { DataStore, DataStoreRecords, PaginationObject } from '../interfaces'; +import PaginationForTable from './PaginationForTable'; +import { getPageInfoFromSearchParams } from '../helpers'; + +export default function DataStoreList() { + const [dataStores, setDataStores] = useState([]); + const [dataStore, setDataStore] = useState(null); + const [pagination, setPagination] = useState(null); + const [results, setResults] = useState([]); + const [searchParams, setSearchParams] = useSearchParams(); + + useEffect(() => { + HttpService.makeCallToBackend({ + path: `/data-stores`, + successCallback: (newStores: DataStore[]) => { + setDataStores(newStores); + }, + }); + }, []); // Do this once so we have a list of data stores to select from. + + useEffect(() => { + const { page, perPage } = getPageInfoFromSearchParams( + searchParams, + 10, + 1, + 'datastore' + ); + console.log(); + const dataStoreType = searchParams.get('type') || ''; + const dataStoreName = searchParams.get('name') || ''; + + if (dataStoreType === '' || dataStoreName === '') { + return; + } + if (dataStores && dataStoreName && dataStoreType) { + dataStores.forEach((ds) => { + if (ds.name === dataStoreName && ds.type === dataStoreType) { + setDataStore(ds); + } + }); + } + const queryParamString = `per_page=${perPage}&page=${page}`; + HttpService.makeCallToBackend({ + path: `/data-stores/${dataStoreType}/${dataStoreName}?${queryParamString}`, + successCallback: (response: DataStoreRecords) => { + setResults(response.results); + setPagination(response.pagination); + }, + }); + }, [dataStores, searchParams]); + + const getTable = () => { + if (results.length === 0) { + return null; + } + const firstResult = results[0]; + console.log('Results', results); + const tableHeaders: any[] = []; + const keys = Object.keys(firstResult); + keys.forEach((key) => tableHeaders.push({key})); + + return ( + + + {tableHeaders} + + + {results.map((object) => { + return ( + + {keys.map((key) => { + return {object[key]}; + })} + + ); + })} + +
+ ); + }; + + const { page, perPage } = getPageInfoFromSearchParams( + searchParams, + 10, + 1, + 'datastore' + ); + return ( + <> + (ds ? `${ds.name} (${ds.type})` : '')} + onChange={(event: any) => { + setDataStore(event.selectedItem); + searchParams.set('datastore_page', '1'); + searchParams.set('datastore_per_page', '10'); + searchParams.set('type', event.selectedItem.type); + searchParams.set('name', event.selectedItem.name); + setSearchParams(searchParams); + }} + /> + + + ); +} diff --git a/spiffworkflow-frontend/src/components/NavigationBar.tsx b/spiffworkflow-frontend/src/components/NavigationBar.tsx index 26a75b3ba..243149326 100644 --- a/spiffworkflow-frontend/src/components/NavigationBar.tsx +++ b/spiffworkflow-frontend/src/components/NavigationBar.tsx @@ -52,6 +52,7 @@ export default function NavigationBar() { [targetUris.authenticationListPath]: ['GET'], [targetUris.messageInstanceListPath]: ['GET'], [targetUris.secretListPath]: ['GET'], + [targetUris.dataStoreListPath]: ['GET'], }; const { ability } = usePermissionFetcher(permissionRequestData); @@ -76,6 +77,8 @@ export default function NavigationBar() { newActiveKey = '/admin/process-instances'; } else if (location.pathname.match(/^\/admin\/configuration\b/)) { newActiveKey = '/admin/configuration'; + } else if (location.pathname.match(/^\/admin\/datastore\b/)) { + newActiveKey = '/admin/datastore'; } else if (location.pathname === '/') { newActiveKey = '/'; } else if (location.pathname.match(/^\/tasks\b/)) { @@ -228,6 +231,14 @@ export default function NavigationBar() { Messages + + + Data Stores + + {configurationElement()} ); diff --git a/spiffworkflow-frontend/src/components/PaginationForTable.tsx b/spiffworkflow-frontend/src/components/PaginationForTable.tsx index 1c86b703a..2544589a5 100644 --- a/spiffworkflow-frontend/src/components/PaginationForTable.tsx +++ b/spiffworkflow-frontend/src/components/PaginationForTable.tsx @@ -44,6 +44,17 @@ export default function PaginationForTable({ }; if (pagination) { + const maxPages = 1000; + const pagesUnknown = pagination.pages > maxPages; + const totalItems = + pagination.pages < maxPages ? pagination.total : maxPages * perPage; + const itemText = () => { + const start = (page - 1) * perPage + 1; + return `Items ${start} to ${start + pagination.count} of ${ + pagination.total + }`; + }; + return ( <> {tableToDisplay} @@ -55,10 +66,12 @@ export default function PaginationForTable({ itemsPerPageText="Items per page:" page={page} pageNumberText="Page Number" + itemText={itemText} pageSize={perPage} pageSizes={perPageOptions || PER_PAGE_OPTIONS} - totalItems={pagination.total} + totalItems={totalItems} onChange={updateRows} + pagesUnknown={pagesUnknown} /> ); diff --git a/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx b/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx index dfce135d0..8b35755c2 100644 --- a/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx +++ b/spiffworkflow-frontend/src/hooks/UriListForPermissions.tsx @@ -7,6 +7,7 @@ export const useUriListForPermissions = () => { return { authenticationListPath: `/v1.0/authentications`, messageInstanceListPath: '/v1.0/messages', + dataStoreListPath: '/v1.0/data-stores', processGroupListPath: '/v1.0/process-groups', processGroupShowPath: `/v1.0/process-groups/${params.process_group_id}`, processInstanceActionPath: `/v1.0/process-instances/${params.process_model_id}/${params.process_instance_id}`, diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index d6a28d5bc..952ab49cb 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -393,3 +393,13 @@ export interface TestCaseResults { failing: TestCaseResult[]; passing: TestCaseResult[]; } + +export interface DataStoreRecords { + results: any[]; + pagination: PaginationObject; +} + +export interface DataStore { + name: string; + type: string; +} diff --git a/spiffworkflow-frontend/src/routes/AdminRoutes.tsx b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx index f42a7de6e..b60eb717e 100644 --- a/spiffworkflow-frontend/src/routes/AdminRoutes.tsx +++ b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx @@ -21,6 +21,7 @@ import ProcessModelNewExperimental from './ProcessModelNewExperimental'; import ProcessInstanceFindById from './ProcessInstanceFindById'; import ProcessInterstitialPage from './ProcessInterstitialPage'; import MessageListPage from './MessageListPage'; +import DataStorePage from './DataStorePage'; export default function AdminRoutes() { const location = useLocation(); @@ -125,6 +126,7 @@ export default function AdminRoutes() { element={} /> } /> + } /> ); diff --git a/spiffworkflow-frontend/src/routes/DataStorePage.tsx b/spiffworkflow-frontend/src/routes/DataStorePage.tsx new file mode 100644 index 000000000..7d04732b2 --- /dev/null +++ b/spiffworkflow-frontend/src/routes/DataStorePage.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import DataStoreList from '../components/DataStoreList'; + +export default function DataStorePage() { + return ( + <> +

Data Stores

+ + + ); +}