ApiLogicServer 15.0.37__py3-none-any.whl → 15.0.40__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. api_logic_server_cli/api_logic_server.py +2 -2
  2. api_logic_server_cli/api_logic_server_info.yaml +2 -2
  3. api_logic_server_cli/genai/genai_svcs.py +4 -3
  4. api_logic_server_cli/prototypes/base/.copilot-instructions.md +10 -0
  5. api_logic_server_cli/prototypes/base/.vscode/.copilot-instructions.md +178 -0
  6. api_logic_server_cli/prototypes/base/README_PROJECT.md +43 -0
  7. api_logic_server_cli/prototypes/base/api_logic_server_run.py +20 -8
  8. api_logic_server_cli/prototypes/base/docs/training/react_map.prompt.md +13 -0
  9. api_logic_server_cli/prototypes/base/docs/training/react_tree.prompt.md +10 -0
  10. api_logic_server_cli/prototypes/base/integration/mcp/readme-mcp.md +9 -0
  11. api_logic_server_cli/prototypes/base/logic/logic_discovery/readme_logic_discovery.md +9 -0
  12. api_logic_server_cli/prototypes/base/logic/logic_discovery/use_case.py +27 -0
  13. api_logic_server_cli/prototypes/base/logic/{readme_declare_logic.md → readme_logic.md} +70 -1
  14. api_logic_server_cli/prototypes/base/readme.md +30 -5
  15. api_logic_server_cli/prototypes/base/security/readme_security.md +17 -0
  16. api_logic_server_cli/prototypes/manager/.copilot-instructions.md +13 -0
  17. api_logic_server_cli/prototypes/manager/.vscode/.copilot-instructions.md +58 -0
  18. api_logic_server_cli/prototypes/manager/.vscode/ApiLogicServer.code-workspace +2 -2
  19. api_logic_server_cli/prototypes/manager/README.md +13 -1
  20. api_logic_server_cli/prototypes/manager/system/Manager_workspace.code-workspace +3 -3
  21. api_logic_server_cli/prototypes/nw/api/api_discovery/authentication_expose_api_models.py +53 -0
  22. api_logic_server_cli/prototypes/nw/api/api_discovery/auto_discovery.py +27 -0
  23. api_logic_server_cli/prototypes/nw/api/api_discovery/count_orders_by_month.html +76 -0
  24. api_logic_server_cli/prototypes/nw/api/api_discovery/count_orders_by_month.sql +1 -0
  25. api_logic_server_cli/prototypes/nw/api/api_discovery/dashboard_services.py +143 -0
  26. api_logic_server_cli/prototypes/nw/api/api_discovery/mcp_discovery.py +97 -0
  27. api_logic_server_cli/prototypes/nw/api/api_discovery/new_service.py +21 -0
  28. api_logic_server_cli/prototypes/nw/api/api_discovery/newer_service.py +21 -0
  29. api_logic_server_cli/prototypes/nw/api/api_discovery/number_of_sales_per_category.html +76 -0
  30. api_logic_server_cli/prototypes/nw/api/api_discovery/number_of_sales_per_category.sql +1 -0
  31. api_logic_server_cli/prototypes/nw/api/api_discovery/ontimize_api.py +495 -0
  32. api_logic_server_cli/prototypes/nw/api/api_discovery/sales_by_category.html +76 -0
  33. api_logic_server_cli/prototypes/nw/api/api_discovery/sales_by_category.sql +1 -0
  34. api_logic_server_cli/prototypes/nw/api/api_discovery/system.py +77 -0
  35. api_logic_server_cli/prototypes/nw/database/database_discovery/graphics_services.py +173 -0
  36. api_logic_server_cli/prototypes/nw/ui/admin/home.js +5 -0
  37. api_logic_server_cli/prototypes/nw/ui/reference_react_app/DEPARTMENT_TREE_VIEW.md +66 -0
  38. api_logic_server_cli/prototypes/nw/ui/reference_react_app/README.md +21 -4
  39. api_logic_server_cli/prototypes/nw/ui/reference_react_app/package.json +4 -0
  40. api_logic_server_cli/prototypes/nw/ui/reference_react_app/public/index.html +3 -0
  41. api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/App.js +8 -1
  42. api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/CustomLayout.js +20 -0
  43. api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Department.js +511 -24
  44. api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/DepartmentTree.js +147 -0
  45. api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Employee.js +230 -18
  46. api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/LandingPage.js +264 -0
  47. api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Supplier.js +359 -121
  48. api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/index.js +1 -0
  49. {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/METADATA +1 -1
  50. {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/RECORD +54 -25
  51. api_logic_server_cli/prototypes/base/docs/training/admin_app_unused.md +0 -156
  52. {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/WHEEL +0 -0
  53. {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/entry_points.txt +0 -0
  54. {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/licenses/LICENSE +0 -0
  55. {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,53 @@
1
+ import inspect
2
+ import safrs
3
+ import importlib
4
+ import pathlib
5
+ import logging as logging
6
+ import flask_sqlalchemy
7
+
8
+ # use absolute path import for easier multi-{app,model,db} support
9
+ force_import = __import__("database.database_discovery.authentication_models")
10
+ database = __import__('database')
11
+ app_logger = logging.getLogger(__name__)
12
+ app_logger.debug("api/expose_api_models.py - endpoint for each table")
13
+
14
+ def add_check_sum(cls):
15
+ """
16
+ Checksum decorator for each model
17
+ """
18
+ @safrs.jsonapi_attr
19
+ def _check_sum_(self):
20
+ if isinstance(self, flask_sqlalchemy.model.DefaultMeta) or not hasattr(self, "_check_sum_property"): # property does not exist during initialization
21
+ return None
22
+ return self._check_sum_property
23
+
24
+ @_check_sum_.setter
25
+ def _check_sum_(self, value):
26
+ self._check_sum_property = value
27
+
28
+ cls.S_CheckSum = _check_sum_
29
+ return cls
30
+
31
+ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators ):
32
+ """ Called by api_discovery to
33
+ * expose API for each model (using introspection)
34
+
35
+ Args:
36
+ app (_type_): _description_
37
+ api (_type_): _description_
38
+ project_dir (_type_): _description_
39
+ swagger_host (str): _description_
40
+ PORT (str): _description_
41
+ method_decorators (_type_): _description_
42
+
43
+ Returns:
44
+ _type_: _description_
45
+ """
46
+
47
+ # Get all the subclasses of the Base class and expose them in the api
48
+ for name, obj in inspect.getmembers(database.database_discovery.authentication_models):
49
+ if inspect.isclass(obj) and issubclass(obj, database.models.SAFRSBaseX) and obj is not database.models.SAFRSBaseX:
50
+ app_logger.info(f"Exposing /{name}")
51
+ api.expose_object(add_check_sum(obj), method_decorators= method_decorators)
52
+
53
+ return api
@@ -0,0 +1,27 @@
1
+ import importlib
2
+ from pathlib import Path
3
+ import logging
4
+
5
+ app_logger = logging.getLogger(__name__)
6
+
7
+ def discover_services(app, api, project_dir, swagger_host: str, PORT: str):
8
+ """
9
+ Discover services in the services directory
10
+ """
11
+ import os
12
+ method_decorators : list = []
13
+ services = []
14
+ services_path = Path(__file__).parent
15
+ for root, dirs, files in os.walk(services_path):
16
+ for file in files:
17
+ if file.endswith(".py"):
18
+ spec = importlib.util.spec_from_file_location("module.name", services_path.joinpath(file))
19
+ if file.endswith("auto_discovery.py"):
20
+ pass
21
+ else:
22
+ services.append(file)
23
+ each_service = importlib.util.module_from_spec(spec)
24
+ spec.loader.exec_module(each_service) # runs "bare" module code (e.g., initialization)
25
+ each_service.add_service(app, api, project_dir, swagger_host, PORT, method_decorators) # invoke create function
26
+ app.logger.info(f"..discovered services: {services}")
27
+ return
@@ -0,0 +1,76 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Bar Chart Example</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ </head>
9
+ <body>
10
+ <canvas id="myChart" width="400" height="200"></canvas>
11
+ <script>
12
+ // Input data
13
+ fetch('http://localhost:5656/api/GraphicsServices/count_orders_by_month', {
14
+ method: 'POST',
15
+ headers: {
16
+ 'accept': 'application/vnd.api+json',
17
+ 'Content-Type': 'application/json'
18
+ },
19
+ body: JSON.stringify({}) // Add any required payload here
20
+ })
21
+ .then(response => {
22
+ if (!response.ok) {
23
+ throw new Error(`HTTP error! status: ${response.status}`);
24
+ }
25
+ return response.json();
26
+ })
27
+ .then(result => {
28
+ const labels = [];
29
+ const data = [];
30
+ const type = result.meta.result.chart_type;
31
+ const title = result.meta.result.title;
32
+ const columns = result.meta.result.columns;
33
+ console.log(JSON.stringify(result));
34
+ result.meta.result.results.forEach(item => {
35
+ labels.push(item[columns[0]]);
36
+ data.push(parseFloat(item[columns[1]]));
37
+ });
38
+
39
+ // Create the bar chart
40
+ const ctx = document.getElementById('myChart').getContext('2d');
41
+ const myChart = new Chart(ctx, {
42
+ type: type,
43
+ data: {
44
+ labels: labels,
45
+ datasets: [{
46
+ label: title,
47
+ data: data,
48
+ backgroundColor: 'rgba(75, 192, 192, 0.2)',
49
+ borderColor: 'rgba(75, 192, 192, 1)',
50
+ borderWidth: 1
51
+ }]
52
+ },
53
+ options: {
54
+ responsive: true,
55
+ plugins: {
56
+ legend: {
57
+ position: 'top',
58
+ },
59
+ title: {
60
+ display: true,
61
+ text: title
62
+ }
63
+ },
64
+ scales: {
65
+ y: {
66
+ beginAtZero: true
67
+ }
68
+ }
69
+ }
70
+ });
71
+ })
72
+ .catch(error => console.error('Error fetching data:', error));
73
+
74
+ </script>
75
+ </body>
76
+ </html>
@@ -0,0 +1 @@
1
+ SELECT strftime('%Y-%m', date(`Order`.OrderDate)) AS OrderMonth, COUNT(`Order`.Id) AS OrderCount FROM `Order` WHERE `Order`.OrderDate IS NOT NULL GROUP BY OrderMonth ORDER BY OrderMonth;
@@ -0,0 +1,143 @@
1
+ from database import models
2
+ import logging
3
+ from safrs import jsonapi_attr
4
+ from sqlalchemy.orm import relationship, remote, foreign
5
+ from functools import wraps # This convenience func preserves name and docstring
6
+ import decimal as decimal
7
+ from sqlalchemy import extract, func
8
+ from flask import request, jsonify
9
+ from safrs import jsonapi_rpc, SAFRSAPI
10
+ from database import models
11
+ import safrs
12
+ from pathlib import Path
13
+
14
+ """
15
+ Called by iFrame in home.js when admin app is loaded
16
+
17
+ Returns iFrame for ui/admin/home.js, with entries like this to populate the dashboard:
18
+ <iframe src="http://localhost:5656/chart_graphics/sales_per_category" style="flex: 1; border: none; width: 90%; height: 200px;">
19
+
20
+ curl -X GET "http://localhost:5656/dashboard"
21
+
22
+ You typically do not alter this file - it is rebuilt on `als genai-graphics`
23
+
24
+ """
25
+
26
+ app_logger = logging.getLogger(__name__)
27
+ dashboard_result = {}
28
+
29
+
30
+ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
31
+ pass
32
+
33
+ @app.route('/dashboard', methods=['GET','OPTIONS'])
34
+ def dashboard():
35
+
36
+
37
+ if request.method == 'OPTIONS':
38
+ return jsonify({"result": "ok"})
39
+
40
+ server = request.host_url
41
+
42
+ iframe_template = '<div class="dashboard-iframe"><iframe src="{url}" style="flex: 1; border: none; width: 90%; height: 200px;"></iframe></div>'
43
+
44
+ iframe_1 = iframe_template.format(url=f"{server}chart_graphics/sales_by_category")
45
+
46
+
47
+ iframe_2 = iframe_template.format(url=f"{server}chart_graphics/number_of_sales_per_category")
48
+
49
+
50
+ iframe_3 = iframe_template.format(url=f"{server}chart_graphics/count_orders_by_month")
51
+
52
+
53
+
54
+
55
+ return f'<div style="display: flex; flex-direction: row; gap: 10px; border: none; ">{iframe_1} {iframe_2} {iframe_3}</div>'
56
+
57
+
58
+
59
+ @app.route('/chart_graphics/<path:path>', methods=['GET','OPTIONS'])
60
+ def chart_graphics(path):
61
+ if request.method == 'OPTIONS':
62
+ return jsonify({"result": "ok"})
63
+
64
+ dashboards = get_dashboards()
65
+ if len(dashboards) == 0:
66
+ return jsonify({"result": "No dashboards Found"}), 404
67
+ if path in dashboards:
68
+ return dashboards[path]
69
+ return jsonify({"result": "not found"}), 404
70
+
71
+
72
+ ##############################
73
+ # generated queries follow
74
+ ##############################
75
+
76
+ def get_dashboards():
77
+ if len(dashboard_result) > 0:
78
+ return dashboard_result
79
+
80
+ from jinja2 import Environment, FileSystemLoader
81
+ env = Environment(loader = FileSystemLoader('ui/templates'))
82
+ template = env.get_template('bar_chart.jinja')
83
+
84
+
85
+
86
+ previously_failed = Path('docs/graphics/sales_by_category.err').exists()
87
+ if previously_failed:
88
+ pass # query has previously failed, so skip it
89
+ else:
90
+ try:
91
+ results = models.Category.sales_by_category(None)
92
+ color = 'rgba(75, 192, 192, 0.2)'
93
+ dashboard1 = template.render(result=results, color=color)
94
+ dashboard_result['sales_by_category']= dashboard1
95
+ except Exception as e:
96
+ msg = f"GenAI query creation error on models.Category.sales_by_category: " + str(e)
97
+ dashboard_result['sales_by_category'] = msg
98
+ app_logger.error(msg)
99
+ with open('docs/graphics//sales_by_category.err', 'w') as err_file:
100
+ err_file.write(msg) # this logs the error to prevent future calls
101
+
102
+
103
+
104
+
105
+ previously_failed = Path('docs/graphics/number_of_sales_per_category.err').exists()
106
+ if previously_failed:
107
+ pass # query has previously failed, so skip it
108
+ else:
109
+ try:
110
+ results = models.Category.number_of_sales_per_category(None)
111
+ color = 'rgba(75, 192, 192, 0.2)'
112
+ dashboard2 = template.render(result=results, color=color)
113
+ dashboard_result['number_of_sales_per_category']= dashboard2
114
+ except Exception as e:
115
+ msg = f"GenAI query creation error on models.Category.number_of_sales_per_category: " + str(e)
116
+ dashboard_result['number_of_sales_per_category'] = msg
117
+ app_logger.error(msg)
118
+ with open('docs/graphics//number_of_sales_per_category.err', 'w') as err_file:
119
+ err_file.write(msg) # this logs the error to prevent future calls
120
+
121
+
122
+
123
+
124
+ previously_failed = Path('docs/graphics/count_orders_by_month.err').exists()
125
+ if previously_failed:
126
+ pass # query has previously failed, so skip it
127
+ else:
128
+ try:
129
+ results = models.Order.count_orders_by_month(None)
130
+ color = 'rgba(75, 192, 192, 0.2)'
131
+ dashboard3 = template.render(result=results, color=color)
132
+ dashboard_result['count_orders_by_month']= dashboard3
133
+ except Exception as e:
134
+ msg = f"GenAI query creation error on models.Order.count_orders_by_month: " + str(e)
135
+ dashboard_result['count_orders_by_month'] = msg
136
+ app_logger.error(msg)
137
+ with open('docs/graphics//count_orders_by_month.err', 'w') as err_file:
138
+ err_file.write(msg) # this logs the error to prevent future calls
139
+
140
+
141
+
142
+
143
+ return dashboard_result
@@ -0,0 +1,97 @@
1
+ from flask import request, jsonify
2
+ import logging
3
+ import os
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import requests
8
+ from config.config import Args # circular import error if at top
9
+
10
+ app_logger = logging.getLogger("api_logic_server_app")
11
+
12
+ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
13
+ pass
14
+
15
+ def get_server_url():
16
+ """ return the server URL for the OpenAPI spec """
17
+ result = f'http://{Args.instance.swagger_host}:{Args.instance.swagger_port}'
18
+ # get env variable API_LOGIC_SERVER_TUNNEL (or None)
19
+ if tunnel_url := os.getenv("API_LOGIC_SERVER_TUNNEL", None):
20
+ app_logger.info(f".. tunnel URL: {tunnel_url}")
21
+ result = tunnel_url
22
+ return result # + '/api'
23
+
24
+
25
+ @app.before_request
26
+ def before_any_request():
27
+ # print(f"[DEBUG] Incoming request: {request.method} {request.url}")
28
+ if activate_openapi_logging := False:
29
+ if request.content_type == 'application/json' and request.method in ['POST', 'PUT', 'PATCH']:
30
+ # openapi: Incoming request: PATCH http://localhost:5656/api/Customer/1/ {'data': {'attributes': {'credit_limit': 5555}, 'type': 'Customer', 'id': '1'}}
31
+ # openapi: Incoming request: PATCH http://6f6f-2601-644-4900-d6f0-ecc9-6df3-8863-c5b2.ngrok-free.app/api/Customer/1 {'credit_limit': 5555}
32
+
33
+ app_logger.info(f"openapi: Incoming request: {request.method} {request.url} {str(request.json)}")
34
+ else:
35
+ app_logger.info(f"openapi: Incoming request: {request.method} {request.url}")
36
+ # app_logger.info(f"openapi: Incoming request headers: {request.headers}")
37
+
38
+ chatgpt_request_json = {
39
+ "credit_limit": 25000,
40
+ }
41
+ standard_request_json = {
42
+ "data": {
43
+ "type": "Customer",
44
+ "id": "ALFKI",
45
+ "attributes": {
46
+ "name": "Alice",
47
+ "credit_limit": 25000,
48
+ "balance": 12345
49
+ }
50
+ }
51
+ }
52
+ swagger_request_json = {
53
+ 'data': {
54
+ 'attributes': {
55
+ 'credit_limit': 5555
56
+ },
57
+ 'type': 'Customer',
58
+ 'id': '1'
59
+ }
60
+ }
61
+ pass
62
+
63
+
64
+ @app.route('/.well-known/mcp.json', methods=['GET'])
65
+ def mcp_discovery(path=None):
66
+ ''' called by mcp_client_executor for discovery; read docs/mcp_learning/ for:
67
+ 1. learning, including fan-out & response format, and email requests.
68
+ 2. schema
69
+
70
+ see: https://apilogicserver.github.io/Docs/Integration-MCP/#1-discovery
71
+
72
+ test: curl -X GET "http://localhost:5656/.well-known/mcp.json"
73
+ '''
74
+ # return docs/mcp_schema.json
75
+ mcp_learning_path = Path(project_dir + "/docs/mcp_learning")
76
+ try:
77
+ schema_path = mcp_learning_path.joinpath('mcp_schema.json')
78
+ if not schema_path.exists():
79
+ return jsonify({"error": "System Error - /docs/mcp_learning/mcp_schema.json not found"}), 404
80
+ with open(schema_path, "r") as schema_file:
81
+ schema = json.load(schema_file)
82
+ except Exception as e:
83
+ app_logger.error(f"Error loading MCP schema: {e}")
84
+ return jsonify({"error": "MCP schema not found"}), 404
85
+
86
+ try:
87
+ learnings_path = mcp_learning_path.joinpath('mcp.prompt')
88
+ if not learnings_path.exists():
89
+ return jsonify({"error": "System Error - /docs/mcp_learning/mcp,prompt not found"}), 404
90
+ with open(learnings_path, "r") as learnings_file:
91
+ learnings = learnings_file.read()
92
+ except Exception as e:
93
+ app_logger.error(f"Error loading MCP learnings: {e}")
94
+ return jsonify({"error": "MCP learnings not found"}), 404
95
+ schema['learning'] = learnings
96
+
97
+ return jsonify(schema), 200
@@ -0,0 +1,21 @@
1
+ from flask import request, jsonify
2
+ import logging
3
+
4
+ app_logger = logging.getLogger("api_logic_server_app")
5
+
6
+ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
7
+ pass
8
+
9
+ @app.route('/hello_service')
10
+ def hello_service():
11
+ """
12
+ Illustrates:
13
+ * Use standard Flask, here for non-database endpoints.
14
+
15
+ Test it with:
16
+
17
+ http://localhost:5656/hello_service?user=ApiLogicServer
18
+ """
19
+ user = request.args.get('user')
20
+ app_logger.info(f'{user}')
21
+ return jsonify({"result": f'hello from new_service! from {user}'})
@@ -0,0 +1,21 @@
1
+ from flask import request, jsonify
2
+ import logging
3
+
4
+ app_logger = logging.getLogger("api_logic_server_app")
5
+
6
+ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators ):
7
+ pass
8
+
9
+ @app.route('/hello_newer_service')
10
+ def hello_newer_service():
11
+ """
12
+ Illustrates:
13
+ * Use standard Flask, here for non-database endpoints.
14
+
15
+ Test it with:
16
+
17
+ http://localhost:5656/hello_newer_service?user=ApiLogicServer
18
+ """
19
+ user = request.args.get('user')
20
+ app_logger.info(f'{user}')
21
+ return jsonify({"result": f'hello from even_newer_service! from {user}'})
@@ -0,0 +1,76 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Bar Chart Example</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ </head>
9
+ <body>
10
+ <canvas id="myChart" width="400" height="200"></canvas>
11
+ <script>
12
+ // Input data
13
+ fetch('http://localhost:5656/api/GraphicsServices/number_of_sales_per_category', {
14
+ method: 'POST',
15
+ headers: {
16
+ 'accept': 'application/vnd.api+json',
17
+ 'Content-Type': 'application/json'
18
+ },
19
+ body: JSON.stringify({}) // Add any required payload here
20
+ })
21
+ .then(response => {
22
+ if (!response.ok) {
23
+ throw new Error(`HTTP error! status: ${response.status}`);
24
+ }
25
+ return response.json();
26
+ })
27
+ .then(result => {
28
+ const labels = [];
29
+ const data = [];
30
+ const type = result.meta.result.chart_type;
31
+ const title = result.meta.result.title;
32
+ const columns = result.meta.result.columns;
33
+ console.log(JSON.stringify(result));
34
+ result.meta.result.results.forEach(item => {
35
+ labels.push(item[columns[0]]);
36
+ data.push(parseFloat(item[columns[1]]));
37
+ });
38
+
39
+ // Create the bar chart
40
+ const ctx = document.getElementById('myChart').getContext('2d');
41
+ const myChart = new Chart(ctx, {
42
+ type: type,
43
+ data: {
44
+ labels: labels,
45
+ datasets: [{
46
+ label: title,
47
+ data: data,
48
+ backgroundColor: 'rgba(75, 192, 192, 0.2)',
49
+ borderColor: 'rgba(75, 192, 192, 1)',
50
+ borderWidth: 1
51
+ }]
52
+ },
53
+ options: {
54
+ responsive: true,
55
+ plugins: {
56
+ legend: {
57
+ position: 'top',
58
+ },
59
+ title: {
60
+ display: true,
61
+ text: title
62
+ }
63
+ },
64
+ scales: {
65
+ y: {
66
+ beginAtZero: true
67
+ }
68
+ }
69
+ }
70
+ });
71
+ })
72
+ .catch(error => console.error('Error fetching data:', error));
73
+
74
+ </script>
75
+ </body>
76
+ </html>
@@ -0,0 +1 @@
1
+ SELECT Category.CategoryName, COUNT(`Order.Id`) AS NumberOfSales FROM Category JOIN Product ON Product.CategoryId = Category.Id JOIN OrderDetail ON OrderDetail.ProductId = Product.Id JOIN `Order` ON `Order`.Id = OrderDetail.OrderId WHERE `Order`.ShippedDate IS NOT NULL GROUP BY Category.CategoryName ORDER BY NumberOfSales DESC;