cognite-neat 0.104.0__py3-none-any.whl → 0.105.0__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.
Potentially problematic release.
This version of cognite-neat might be problematic. Click here for more details.
- cognite/neat/_client/_api/data_modeling_loaders.py +83 -23
- cognite/neat/_client/_api/schema.py +2 -1
- cognite/neat/_client/data_classes/neat_sequence.py +261 -0
- cognite/neat/_client/data_classes/schema.py +5 -1
- cognite/neat/_client/testing.py +33 -0
- cognite/neat/_constants.py +56 -0
- cognite/neat/_graph/extractors/_classic_cdf/_base.py +6 -5
- cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +225 -11
- cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
- cognite/neat/_graph/loaders/_rdf2dms.py +13 -2
- cognite/neat/_graph/transformers/__init__.py +3 -1
- cognite/neat/_graph/transformers/_classic_cdf.py +2 -1
- cognite/neat/_graph/transformers/_value_type.py +72 -0
- cognite/neat/_issues/__init__.py +0 -2
- cognite/neat/_issues/_base.py +19 -35
- cognite/neat/_issues/warnings/__init__.py +4 -1
- cognite/neat/_issues/warnings/_general.py +7 -0
- cognite/neat/_issues/warnings/_resources.py +11 -0
- cognite/neat/_rules/exporters/_rules2dms.py +35 -1
- cognite/neat/_rules/exporters/_rules2excel.py +2 -2
- cognite/neat/_rules/importers/_dms2rules.py +66 -55
- cognite/neat/_rules/models/_base_rules.py +4 -1
- cognite/neat/_rules/models/entities/_wrapped.py +10 -5
- cognite/neat/_rules/models/mapping/_classic2core.yaml +239 -38
- cognite/neat/_rules/transformers/__init__.py +8 -2
- cognite/neat/_rules/transformers/_converters.py +271 -188
- cognite/neat/_rules/transformers/_mapping.py +75 -59
- cognite/neat/_rules/transformers/_verification.py +2 -3
- cognite/neat/_session/_inspect.py +3 -1
- cognite/neat/_session/_prepare.py +112 -24
- cognite/neat/_session/_read.py +33 -70
- cognite/neat/_session/_state.py +2 -2
- cognite/neat/_session/_to.py +2 -2
- cognite/neat/_store/_rules_store.py +4 -8
- cognite/neat/_utils/reader/_base.py +27 -0
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.0.dist-info}/METADATA +3 -2
- cognite_neat-0.105.0.dist-info/RECORD +179 -0
- {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.0.dist-info}/WHEEL +1 -1
- cognite/neat/_app/api/__init__.py +0 -0
- cognite/neat/_app/api/asgi/metrics.py +0 -4
- cognite/neat/_app/api/configuration.py +0 -98
- cognite/neat/_app/api/context_manager/__init__.py +0 -3
- cognite/neat/_app/api/context_manager/manager.py +0 -16
- cognite/neat/_app/api/data_classes/__init__.py +0 -0
- cognite/neat/_app/api/data_classes/rest.py +0 -59
- cognite/neat/_app/api/explorer.py +0 -66
- cognite/neat/_app/api/routers/configuration.py +0 -25
- cognite/neat/_app/api/routers/crud.py +0 -102
- cognite/neat/_app/api/routers/metrics.py +0 -10
- cognite/neat/_app/api/routers/workflows.py +0 -224
- cognite/neat/_app/api/utils/__init__.py +0 -0
- cognite/neat/_app/api/utils/data_mapping.py +0 -17
- cognite/neat/_app/api/utils/logging.py +0 -26
- cognite/neat/_app/api/utils/query_templates.py +0 -92
- cognite/neat/_app/main.py +0 -17
- cognite/neat/_app/monitoring/__init__.py +0 -0
- cognite/neat/_app/monitoring/metrics.py +0 -69
- cognite/neat/_app/ui/index.html +0 -1
- cognite/neat/_app/ui/neat-app/.gitignore +0 -23
- cognite/neat/_app/ui/neat-app/README.md +0 -70
- cognite/neat/_app/ui/neat-app/build/asset-manifest.json +0 -14
- cognite/neat/_app/ui/neat-app/build/favicon.ico +0 -0
- cognite/neat/_app/ui/neat-app/build/img/architect-icon.svg +0 -116
- cognite/neat/_app/ui/neat-app/build/img/developer-icon.svg +0 -112
- cognite/neat/_app/ui/neat-app/build/img/sme-icon.svg +0 -34
- cognite/neat/_app/ui/neat-app/build/index.html +0 -1
- cognite/neat/_app/ui/neat-app/build/logo192.png +0 -0
- cognite/neat/_app/ui/neat-app/build/manifest.json +0 -25
- cognite/neat/_app/ui/neat-app/build/robots.txt +0 -3
- cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css +0 -2
- cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css.map +0 -1
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js +0 -3
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.LICENSE.txt +0 -88
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.map +0 -1
- cognite/neat/_app/ui/neat-app/build/static/media/logo.8093b84df9ed36a174c629d6fe0b730d.svg +0 -1
- cognite/neat/_app/ui/neat-app/package-lock.json +0 -18306
- cognite/neat/_app/ui/neat-app/package.json +0 -62
- cognite/neat/_app/ui/neat-app/public/favicon.ico +0 -0
- cognite/neat/_app/ui/neat-app/public/img/architect-icon.svg +0 -116
- cognite/neat/_app/ui/neat-app/public/img/developer-icon.svg +0 -112
- cognite/neat/_app/ui/neat-app/public/img/sme-icon.svg +0 -34
- cognite/neat/_app/ui/neat-app/public/index.html +0 -43
- cognite/neat/_app/ui/neat-app/public/logo192.png +0 -0
- cognite/neat/_app/ui/neat-app/public/manifest.json +0 -25
- cognite/neat/_app/ui/neat-app/public/robots.txt +0 -3
- cognite/neat/_app/ui/neat-app/src/App.css +0 -38
- cognite/neat/_app/ui/neat-app/src/App.js +0 -17
- cognite/neat/_app/ui/neat-app/src/App.test.js +0 -8
- cognite/neat/_app/ui/neat-app/src/MainContainer.tsx +0 -70
- cognite/neat/_app/ui/neat-app/src/components/JsonViewer.tsx +0 -43
- cognite/neat/_app/ui/neat-app/src/components/LocalUploader.tsx +0 -124
- cognite/neat/_app/ui/neat-app/src/components/OverviewComponentEditorDialog.tsx +0 -63
- cognite/neat/_app/ui/neat-app/src/components/StepEditorDialog.tsx +0 -511
- cognite/neat/_app/ui/neat-app/src/components/TabPanel.tsx +0 -36
- cognite/neat/_app/ui/neat-app/src/components/Utils.tsx +0 -56
- cognite/neat/_app/ui/neat-app/src/components/WorkflowDeleteDialog.tsx +0 -60
- cognite/neat/_app/ui/neat-app/src/components/WorkflowExecutionReport.tsx +0 -112
- cognite/neat/_app/ui/neat-app/src/components/WorkflowImportExportDialog.tsx +0 -67
- cognite/neat/_app/ui/neat-app/src/components/WorkflowMetadataDialog.tsx +0 -79
- cognite/neat/_app/ui/neat-app/src/index.css +0 -13
- cognite/neat/_app/ui/neat-app/src/index.js +0 -13
- cognite/neat/_app/ui/neat-app/src/logo.svg +0 -1
- cognite/neat/_app/ui/neat-app/src/reportWebVitals.js +0 -13
- cognite/neat/_app/ui/neat-app/src/setupTests.js +0 -5
- cognite/neat/_app/ui/neat-app/src/types/WorkflowTypes.ts +0 -388
- cognite/neat/_app/ui/neat-app/src/views/AboutView.tsx +0 -61
- cognite/neat/_app/ui/neat-app/src/views/ConfigView.tsx +0 -184
- cognite/neat/_app/ui/neat-app/src/views/GlobalConfigView.tsx +0 -180
- cognite/neat/_app/ui/neat-app/src/views/WorkflowView.tsx +0 -570
- cognite/neat/_app/ui/neat-app/tsconfig.json +0 -27
- cognite/neat/_workflows/__init__.py +0 -17
- cognite/neat/_workflows/base.py +0 -590
- cognite/neat/_workflows/cdf_store.py +0 -393
- cognite/neat/_workflows/examples/Export_DMS/workflow.yaml +0 -89
- cognite/neat/_workflows/examples/Export_Semantic_Data_Model/workflow.yaml +0 -66
- cognite/neat/_workflows/examples/Import_DMS/workflow.yaml +0 -65
- cognite/neat/_workflows/examples/Validate_Rules/workflow.yaml +0 -67
- cognite/neat/_workflows/examples/Validate_Solution_Model/workflow.yaml +0 -64
- cognite/neat/_workflows/manager.py +0 -292
- cognite/neat/_workflows/model.py +0 -203
- cognite/neat/_workflows/steps/__init__.py +0 -0
- cognite/neat/_workflows/steps/data_contracts.py +0 -109
- cognite/neat/_workflows/steps/lib/__init__.py +0 -0
- cognite/neat/_workflows/steps/lib/current/__init__.py +0 -6
- cognite/neat/_workflows/steps/lib/current/graph_extractor.py +0 -100
- cognite/neat/_workflows/steps/lib/current/graph_loader.py +0 -51
- cognite/neat/_workflows/steps/lib/current/graph_store.py +0 -48
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +0 -537
- cognite/neat/_workflows/steps/lib/current/rules_importer.py +0 -323
- cognite/neat/_workflows/steps/lib/current/rules_validator.py +0 -106
- cognite/neat/_workflows/steps/lib/io/__init__.py +0 -1
- cognite/neat/_workflows/steps/lib/io/io_steps.py +0 -393
- cognite/neat/_workflows/steps/step_model.py +0 -79
- cognite/neat/_workflows/steps_registry.py +0 -218
- cognite/neat/_workflows/tasks.py +0 -18
- cognite/neat/_workflows/triggers.py +0 -169
- cognite/neat/_workflows/utils.py +0 -19
- cognite_neat-0.104.0.dist-info/RECORD +0 -276
- {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# IO, CDF, Workflows
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import logging
|
|
5
|
-
import shutil
|
|
6
|
-
|
|
7
|
-
from fastapi import APIRouter, UploadFile
|
|
8
|
-
|
|
9
|
-
from cognite.neat._app.api.configuration import NEAT_APP
|
|
10
|
-
from cognite.neat._config import Config
|
|
11
|
-
from cognite.neat._workflows.model import FlowMessage
|
|
12
|
-
from cognite.neat._workflows.utils import get_file_hash
|
|
13
|
-
|
|
14
|
-
router = APIRouter()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@router.post("/api/file/upload/{workflow_name}/{file_type}/{step_id}/{action}")
|
|
18
|
-
async def file_upload_handler(
|
|
19
|
-
files: list[UploadFile],
|
|
20
|
-
workflow_name: str,
|
|
21
|
-
file_type: str,
|
|
22
|
-
step_id: str,
|
|
23
|
-
action: str,
|
|
24
|
-
) -> dict[str, str]:
|
|
25
|
-
if NEAT_APP.cdf_store is None or NEAT_APP.workflow_manager is None:
|
|
26
|
-
return {"error": "NeatApp is not initialized"}
|
|
27
|
-
if NEAT_APP.workflow_manager.data_store_path is None:
|
|
28
|
-
return {"error": "Workflow Manager is not initialized"}
|
|
29
|
-
|
|
30
|
-
# get directory path
|
|
31
|
-
upload_dir = NEAT_APP.config.rules_store_path
|
|
32
|
-
file_name = ""
|
|
33
|
-
file_version = ""
|
|
34
|
-
if file_type == "file_from_editor":
|
|
35
|
-
upload_dir = NEAT_APP.workflow_manager.config.workflows_store_path / workflow_name
|
|
36
|
-
elif file_type == "workflow":
|
|
37
|
-
upload_dir = NEAT_APP.workflow_manager.config.workflows_store_path
|
|
38
|
-
elif file_type == "staging":
|
|
39
|
-
upload_dir = NEAT_APP.workflow_manager.config.staging_path
|
|
40
|
-
elif file_type == "source_graph":
|
|
41
|
-
upload_dir = NEAT_APP.workflow_manager.config.source_graph_path
|
|
42
|
-
|
|
43
|
-
for file in files:
|
|
44
|
-
logging.info(
|
|
45
|
-
f"Uploading file : {file.filename} , workflow : {workflow_name} , step_id {step_id} , action : {action}"
|
|
46
|
-
)
|
|
47
|
-
# save file to disk
|
|
48
|
-
if file.filename:
|
|
49
|
-
if file_type == "global_config":
|
|
50
|
-
full_path = NEAT_APP.config.data_store_path / "config.yaml"
|
|
51
|
-
else:
|
|
52
|
-
full_path = upload_dir / file.filename
|
|
53
|
-
with full_path.open("wb") as buffer:
|
|
54
|
-
shutil.copyfileobj(file.file, buffer)
|
|
55
|
-
file_name = file.filename
|
|
56
|
-
file_version = get_file_hash(full_path)
|
|
57
|
-
break # only one file is supported for now
|
|
58
|
-
|
|
59
|
-
if "update_config" in action and file_type == "rules":
|
|
60
|
-
logging.info("Automatically updating workflow config")
|
|
61
|
-
workflow = NEAT_APP.workflow_manager.get_workflow(workflow_name)
|
|
62
|
-
if workflow is None:
|
|
63
|
-
return {"error": f"Workflow {workflow_name} not found"}
|
|
64
|
-
workflow_definition = workflow.get_workflow_definition()
|
|
65
|
-
|
|
66
|
-
for step in workflow_definition.steps:
|
|
67
|
-
if step.method == "ImportExcelToRules":
|
|
68
|
-
step.configs["file_name"] = file_name
|
|
69
|
-
step.configs["version"] = ""
|
|
70
|
-
|
|
71
|
-
NEAT_APP.workflow_manager.save_workflow_to_storage(workflow_name)
|
|
72
|
-
|
|
73
|
-
if ("start_workflow" in action and file_type == "rules") or file_type == "staging":
|
|
74
|
-
logging.info("Starting workflow after file upload")
|
|
75
|
-
workflow = NEAT_APP.workflow_manager.get_workflow(workflow_name)
|
|
76
|
-
if workflow is None:
|
|
77
|
-
return {"error": f"Workflow {workflow_name} not found"}
|
|
78
|
-
flow_msg = FlowMessage(
|
|
79
|
-
payload={
|
|
80
|
-
"file_name": file_name,
|
|
81
|
-
"hash": file_version,
|
|
82
|
-
"full_path": full_path,
|
|
83
|
-
"file_type": file_type,
|
|
84
|
-
}
|
|
85
|
-
)
|
|
86
|
-
start_step_id = None if step_id == "none" else step_id
|
|
87
|
-
|
|
88
|
-
workflow.start(sync=False, flow_message=flow_msg, start_step_id=start_step_id)
|
|
89
|
-
|
|
90
|
-
if action == "install" and file_type == "workflow":
|
|
91
|
-
logging.info("Installing workflow after file upload")
|
|
92
|
-
NEAT_APP.cdf_store.extract_workflow_package(file_name)
|
|
93
|
-
|
|
94
|
-
if file_type == "global_config":
|
|
95
|
-
logging.info("Updating global config and restarting NeatApp")
|
|
96
|
-
config = Config.from_yaml(full_path)
|
|
97
|
-
config.data_store_path = NEAT_APP.config.data_store_path
|
|
98
|
-
NEAT_APP.stop()
|
|
99
|
-
NEAT_APP.start(config=config)
|
|
100
|
-
logging.info("NeatApp restarted")
|
|
101
|
-
|
|
102
|
-
return {"file_name": file_name, "hash": file_version}
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import shutil
|
|
3
|
-
from typing import cast
|
|
4
|
-
|
|
5
|
-
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile
|
|
6
|
-
from fastapi.responses import FileResponse, JSONResponse
|
|
7
|
-
|
|
8
|
-
from cognite.neat._app.api.configuration import NEAT_APP
|
|
9
|
-
from cognite.neat._app.api.data_classes.rest import (
|
|
10
|
-
RunWorkflowRequest,
|
|
11
|
-
)
|
|
12
|
-
from cognite.neat._workflows import WorkflowFullStateReport
|
|
13
|
-
from cognite.neat._workflows.base import WorkflowDefinition
|
|
14
|
-
from cognite.neat._workflows.model import FlowMessage
|
|
15
|
-
from cognite.neat._workflows.steps.data_contracts import NeatGraph
|
|
16
|
-
from cognite.neat._workflows.steps.step_model import DataContract
|
|
17
|
-
|
|
18
|
-
router = APIRouter()
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@router.post("/api/workflow/start")
|
|
22
|
-
def start_workflow(request: RunWorkflowRequest):
|
|
23
|
-
logging.info("Starting workflow endpoint")
|
|
24
|
-
if NEAT_APP.workflow_manager is None:
|
|
25
|
-
return {"error": "NeatApp is not initialized"}
|
|
26
|
-
start_status = NEAT_APP.workflow_manager.start_workflow_instance(
|
|
27
|
-
request.name, sync=request.sync, flow_msg=FlowMessage()
|
|
28
|
-
)
|
|
29
|
-
result = {
|
|
30
|
-
"workflow_instance": None,
|
|
31
|
-
"is_success": start_status.is_success,
|
|
32
|
-
"status_text": start_status.status_text,
|
|
33
|
-
}
|
|
34
|
-
return {"result": result}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@router.get("/api/workflow/stats/{workflow_name}", response_model=WorkflowFullStateReport)
|
|
38
|
-
def get_workflow_stats(
|
|
39
|
-
workflow_name: str,
|
|
40
|
-
) -> WorkflowFullStateReport | None | dict[str, str]:
|
|
41
|
-
if NEAT_APP.workflow_manager is None:
|
|
42
|
-
return {"error": "NeatApp is not initialized"}
|
|
43
|
-
workflow = NEAT_APP.workflow_manager.get_workflow(workflow_name)
|
|
44
|
-
if workflow is None:
|
|
45
|
-
raise HTTPException(status_code=404, detail="workflow not found")
|
|
46
|
-
return workflow.get_state()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@router.get("/api/workflow/workflows")
|
|
50
|
-
def get_workflows():
|
|
51
|
-
return {"workflows": NEAT_APP.workflow_manager.get_list_of_workflows()}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@router.get("/api/workflow/files/{workflow_name}")
|
|
55
|
-
def get_workflow_files(workflow_name: str):
|
|
56
|
-
if NEAT_APP.workflow_manager is None:
|
|
57
|
-
return {"error": "NeatApp is not initialized"}
|
|
58
|
-
workflow = NEAT_APP.workflow_manager.get_workflow(workflow_name)
|
|
59
|
-
if workflow is None:
|
|
60
|
-
raise HTTPException(status_code=404, detail="workflow not found")
|
|
61
|
-
return {"files": workflow.get_list_of_workflow_artifacts()}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
@router.post("/api/workflow/create")
|
|
65
|
-
def create_new_workflow(request: WorkflowDefinition):
|
|
66
|
-
if NEAT_APP.workflow_manager is None:
|
|
67
|
-
return {"error": "NeatApp is not initialized"}
|
|
68
|
-
new_workflow_definition = NEAT_APP.workflow_manager.create_new_workflow(
|
|
69
|
-
request.name, request.description, "manifest"
|
|
70
|
-
)
|
|
71
|
-
return {"workflow": new_workflow_definition}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
@router.delete("/api/workflow/{workflow_name}")
|
|
75
|
-
def delete_workflow(workflow_name: str):
|
|
76
|
-
if NEAT_APP.workflow_manager is None:
|
|
77
|
-
return {"error": "NeatApp is not initialized"}
|
|
78
|
-
NEAT_APP.workflow_manager.delete_workflow(workflow_name)
|
|
79
|
-
return {"result": "ok"}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@router.post("/api/workflow/reload-workflows")
|
|
83
|
-
def reload_workflows():
|
|
84
|
-
NEAT_APP.workflow_manager.load_workflows_from_storage()
|
|
85
|
-
NEAT_APP.triggers_manager.reload_all_triggers()
|
|
86
|
-
return {
|
|
87
|
-
"result": "ok",
|
|
88
|
-
"workflows": NEAT_APP.workflow_manager.get_list_of_workflows(),
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@router.post("/api/workflow/reload-single-workflow/{workflow_name}")
|
|
93
|
-
def reload_single_workflows(workflow_name: str):
|
|
94
|
-
NEAT_APP.workflow_manager.load_single_workflow_from_storage(workflow_name)
|
|
95
|
-
NEAT_APP.triggers_manager.reload_all_triggers()
|
|
96
|
-
return {
|
|
97
|
-
"result": "ok",
|
|
98
|
-
"workflows": NEAT_APP.workflow_manager.get_list_of_workflows(),
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@router.get("/api/workflow/workflow-definition/{workflow_name}")
|
|
103
|
-
def get_workflow_definition(workflow_name: str):
|
|
104
|
-
if NEAT_APP.workflow_manager is None:
|
|
105
|
-
return {"error": "NeatApp is not initialized"}
|
|
106
|
-
workflow = NEAT_APP.workflow_manager.get_workflow(workflow_name)
|
|
107
|
-
if workflow is None:
|
|
108
|
-
return {"error": "Workflow is not initialized"}
|
|
109
|
-
return {"definition": workflow.get_workflow_definition()}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
@router.get("/api/workflow/workflow-src/{workflow_name}/{file_name}")
|
|
113
|
-
def get_workflow_src(workflow_name: str, file_name: str):
|
|
114
|
-
if NEAT_APP.workflow_manager is None:
|
|
115
|
-
return {"error": "NeatApp is not initialized"}
|
|
116
|
-
# Todo: Thi sis a bug in the API. The method below does not exist
|
|
117
|
-
src = NEAT_APP.workflow_manager.get_workflow_src(workflow_name, file_name=file_name) # type: ignore[attr-defined]
|
|
118
|
-
return FileResponse(src, media_type="text/plain")
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
@router.post("/api/workflow/workflow-definition/{workflow_name}")
|
|
122
|
-
def update_workflow_definition(workflow_name: str, request: WorkflowDefinition):
|
|
123
|
-
if NEAT_APP.workflow_manager is None:
|
|
124
|
-
return {"error": "NeatApp is not initialized"}
|
|
125
|
-
wf = NEAT_APP.workflow_manager.get_workflow(workflow_name)
|
|
126
|
-
if wf is not None:
|
|
127
|
-
wf.cleanup_workflow_context()
|
|
128
|
-
NEAT_APP.workflow_manager.update_workflow(workflow_name, request)
|
|
129
|
-
NEAT_APP.workflow_manager.save_workflow_to_storage(workflow_name)
|
|
130
|
-
return {"result": "ok"}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
@router.get("/api/workflow/context/{workflow_name}")
|
|
134
|
-
def get_context(workflow_name: str):
|
|
135
|
-
if NEAT_APP.workflow_manager is None:
|
|
136
|
-
return {"error": "Workflow Manager is not initialized"}
|
|
137
|
-
workflow = NEAT_APP.workflow_manager.get_workflow(workflow_name)
|
|
138
|
-
if workflow is None:
|
|
139
|
-
return {"error": "Workflow is not initialized"}
|
|
140
|
-
context = workflow.get_context()
|
|
141
|
-
objects_in_context = []
|
|
142
|
-
for key, value in context.items():
|
|
143
|
-
objects_in_context.append({"name": key, "type": type(value).__name__})
|
|
144
|
-
return {"context": objects_in_context}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
@router.get("/api/workflow/context/{workflow_name}/object_name/{object_name}")
|
|
148
|
-
def get_context_object(workflow_name: str, object_name: str):
|
|
149
|
-
"""Get context item from workflow. Should be used for debugging and troubleshooting only."""
|
|
150
|
-
workflow = NEAT_APP.workflow_manager.get_workflow(workflow_name)
|
|
151
|
-
if workflow is None:
|
|
152
|
-
return
|
|
153
|
-
context = workflow.get_context()
|
|
154
|
-
if object_name not in context:
|
|
155
|
-
return {"error": f"Item {object_name} is not found in workflow context"}
|
|
156
|
-
|
|
157
|
-
if object_name == "NeatGraph":
|
|
158
|
-
return {"object": cast(NeatGraph, context[object_name]).graph.type_}
|
|
159
|
-
|
|
160
|
-
cobject = context[object_name]
|
|
161
|
-
if isinstance(cobject, DataContract):
|
|
162
|
-
return {"object": cobject.model_dump()}
|
|
163
|
-
else:
|
|
164
|
-
return {"object": cobject}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
@router.get("/api/workflow/registered-steps")
|
|
168
|
-
def get_steps():
|
|
169
|
-
steps_registry = NEAT_APP.workflow_manager.get_steps_registry()
|
|
170
|
-
return {"steps": steps_registry.get_list_of_steps()}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
@router.post("/api/workflow/file/{workflow_name}")
|
|
174
|
-
async def upload_file(file: UploadFile, workflow_name: str):
|
|
175
|
-
if NEAT_APP.workflow_manager is None or NEAT_APP.workflow_manager.data_store_path is None:
|
|
176
|
-
return JSONResponse(content={"error": "Workflow Manager is not initialized"}, status_code=400)
|
|
177
|
-
try:
|
|
178
|
-
upload_dir = NEAT_APP.workflow_manager.config.workflows_store_path / workflow_name
|
|
179
|
-
# Create a directory to store uploaded files if it doesn't exist
|
|
180
|
-
|
|
181
|
-
# Define the file path where the uploaded file will be saved
|
|
182
|
-
if file.filename is None:
|
|
183
|
-
return JSONResponse(content={"message": "File name is not provided"}, status_code=400)
|
|
184
|
-
file_path = upload_dir / file.filename
|
|
185
|
-
|
|
186
|
-
# Save the uploaded file to the specified path
|
|
187
|
-
with file_path.open("wb") as buffer:
|
|
188
|
-
shutil.copyfileobj(file.file, buffer)
|
|
189
|
-
|
|
190
|
-
if file.filename.endswith(".py"):
|
|
191
|
-
NEAT_APP.workflow_manager.steps_registry.load_workflow_step_classes(workflow_name)
|
|
192
|
-
|
|
193
|
-
return JSONResponse(content={"message": "File uploaded successfully"}, status_code=200)
|
|
194
|
-
except Exception as e:
|
|
195
|
-
return JSONResponse(content={"message": f"An error occurred: {e!s}"}, status_code=500)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
async def get_body(request: Request):
|
|
199
|
-
return await request.body()
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
fast_api_depends = Depends(get_body)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
@router.post("/api/workflow/{workflow_name}/http_trigger/{step_id}")
|
|
206
|
-
def http_trigger_start_workflow(workflow_name: str, step_id: str, request: Request, body: bytes = fast_api_depends):
|
|
207
|
-
if NEAT_APP.triggers_manager is None:
|
|
208
|
-
return JSONResponse(content={"error": "Triggers Manager is not initialized"}, status_code=400)
|
|
209
|
-
return NEAT_APP.triggers_manager.start_workflow_from_http_request(workflow_name, step_id, request, body)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
@router.post("/api/workflow/{workflow_name}/resume/{step_id}/{instance_id}")
|
|
213
|
-
def http_trigger_resume_workflow(
|
|
214
|
-
workflow_name: str,
|
|
215
|
-
step_id: str,
|
|
216
|
-
instance_id: str,
|
|
217
|
-
request: Request,
|
|
218
|
-
body: bytes = fast_api_depends,
|
|
219
|
-
):
|
|
220
|
-
if NEAT_APP.triggers_manager is None:
|
|
221
|
-
return JSONResponse(content={"error": "Triggers Manager is not initialized"}, status_code=400)
|
|
222
|
-
return NEAT_APP.triggers_manager.resume_workflow_from_http_request(
|
|
223
|
-
workflow_name, step_id, instance_id, request, body
|
|
224
|
-
)
|
|
File without changes
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from typing import Any, cast
|
|
2
|
-
|
|
3
|
-
from rdflib.query import Result
|
|
4
|
-
from rdflib.term import Node
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def rdf_result_to_api_response(result: Result) -> dict[str, Any]:
|
|
8
|
-
response: dict[str, Any] = {"fields": [], "rows": []}
|
|
9
|
-
if result.vars is None:
|
|
10
|
-
result.vars = ["s", "p", "o"] # type: ignore[list-item]
|
|
11
|
-
|
|
12
|
-
for field in result.vars:
|
|
13
|
-
response["fields"].append(field)
|
|
14
|
-
for row in result:
|
|
15
|
-
rrow = {field: cast(tuple[Node, Node, Node], row)[i] for i, field in enumerate(result.vars)}
|
|
16
|
-
response["rows"].append(rrow)
|
|
17
|
-
return response
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class EndpointFilter(logging.Filter):
|
|
5
|
-
"""Filter class to exclude specific endpoints from log entries."""
|
|
6
|
-
|
|
7
|
-
def __init__(self, excluded_endpoints: list[str]) -> None:
|
|
8
|
-
"""
|
|
9
|
-
Initialize the EndpointFilter class.
|
|
10
|
-
|
|
11
|
-
Args:
|
|
12
|
-
excluded_endpoints: A list of endpoints to be excluded from log entries.
|
|
13
|
-
"""
|
|
14
|
-
self.excluded_endpoints = excluded_endpoints
|
|
15
|
-
|
|
16
|
-
def filter(self, record: logging.LogRecord) -> bool:
|
|
17
|
-
"""
|
|
18
|
-
Filter out log entries for excluded endpoints.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
record: The log record to be filtered.
|
|
22
|
-
|
|
23
|
-
Returns:
|
|
24
|
-
bool: True if the log entry should be included, False otherwise.
|
|
25
|
-
"""
|
|
26
|
-
return all(endpoint not in record.getMessage() for endpoint in self.excluded_endpoints)
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
query_templates = [
|
|
2
|
-
{"name": "Get all classes", "query": "SELECT DISTINCT ?class WHERE { ?s a ?class }"},
|
|
3
|
-
{
|
|
4
|
-
"name": "Get all classes with stats",
|
|
5
|
-
"query": "SELECT ?class (count(?s) as ?instances ) "
|
|
6
|
-
"WHERE { ?s a ?class . } group by ?class order by DESC(?instances)",
|
|
7
|
-
},
|
|
8
|
-
{"name": "Describe object", "query": " DESCRIBE <http://cog.com/neat#_30b297b4-8e19-da40-9f52-fb9175136a22>"},
|
|
9
|
-
{"name": "Count all classes", "query": " SELECT (COUNT(DISTINCT ?class) AS ?count) WHERE { ?s a ?class }"},
|
|
10
|
-
{"name": "Get all instances", "query": " SELECT DISTINCT ?s ?class WHERE { ?s a ?class }"},
|
|
11
|
-
{"name": "Count all instances", "query": " SELECT (COUNT(DISTINCT ?s) AS ?count) WHERE { ?s a ?class }"},
|
|
12
|
-
{"name": "Get all properties", "query": " SELECT DISTINCT ?p WHERE { ?s ?p ?o }"},
|
|
13
|
-
{"name": "Count all properties", "query": " SELECT (COUNT(DISTINCT ?p) AS ?count) WHERE { ?s ?p ?o }"},
|
|
14
|
-
{
|
|
15
|
-
"name": "Get all properties of a class",
|
|
16
|
-
"query": " SELECT DISTINCT ?property WHERE { ?s a ?class . ?s ?property ?o }",
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"name": "Get all properties of an instance",
|
|
20
|
-
"query": " SELECT DISTINCT ?property WHERE { ?instance a ?class . ?instance ?property ?o }",
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"name": "Get all properties of a class and their values",
|
|
24
|
-
"query": " SELECT DISTINCT ?property ?value WHERE { ?s a ?class . ?s ?property ?value }",
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"name": "Get all properties of an instance and their values",
|
|
28
|
-
"query": " SELECT DISTINCT ?property ?value WHERE { ?instance a ?class . ?instance ?property ?value }",
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"name": "Get all properties of a class and their values (with type)",
|
|
32
|
-
"query": """ SELECT DISTINCT ?property ?value ?type
|
|
33
|
-
WHERE { ?s a ?class . ?s ?property ?value . ?value a ?type }""",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"name": "Get all properties of an instance and their values (with type)",
|
|
37
|
-
"query": """ SELECT DISTINCT ?property ?value ?type
|
|
38
|
-
WHERE {?instance a ?class . ?instance ?property ?value . ?value a ?type }""",
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"name": "Get all properties of a class and their values (with type and label)",
|
|
42
|
-
"query": """ SELECT DISTINCT ?property ?value ?type ?label
|
|
43
|
-
WHERE { ?s a ?class . ?s ?property ?value . ?value a ?type . ?value rdfs:label ?label }""",
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"name": "Get all properties of an instance and their values (with type and label)",
|
|
47
|
-
"query": """ SELECT DISTINCT ?property ?value ?type ?label
|
|
48
|
-
WHERE { ?instance a ?class . ?instance ?property ?value . ?value a ?type . ?value rdfs:label ?label }""",
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
"name": "Get all properties of a class and their values (with type and label) (with type and label)",
|
|
52
|
-
"query": """ SELECT DISTINCT ?property ?value ?type ?label
|
|
53
|
-
WHERE { ?s a ?class . ?s ?property ?value . ?value a ?type . ?value rdfs:label ?label }""",
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"name": "Get all properties of an instance and their values (with type and label) (with type and label)",
|
|
57
|
-
"query": """ SELECT DISTINCT ?property ?value ?type ?label
|
|
58
|
-
WHERE { ?instance a ?class . ?instance ?property ?value . ?value a ?type . ?value rdfs:label ?label }""",
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"name": "Get list of all properties of Substations",
|
|
62
|
-
"query": "SELECT ?subject ?p ?object "
|
|
63
|
-
"WHERE { ?subject a cim:Substation . ?subject ?p ?object . } "
|
|
64
|
-
"order by ?subject limit 12",
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
"name": "Get all properties of the node",
|
|
68
|
-
"query": "SELECT ?predicate ?object "
|
|
69
|
-
"WHERE { <http://neat-cog.com/#_fbf1e5dc-2ce7-ec2a-e040-1e828c9489bf> ?predicate ?object . }",
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
"name": "Get all properties of the node and performing 2 level traversal",
|
|
73
|
-
"query": "SELECT ?property1 ?value1 ?property2 ?value2 WHERE "
|
|
74
|
-
"{ <http://neat-cog.com/#_fbf1e5dc-2ce7-ec2a-e040-1e828c9489bf> ?property1 ?value1 . "
|
|
75
|
-
"OPTIONAL { ?value1 ?property2 ?value2 } }",
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
"name": "Get graph compatible query",
|
|
79
|
-
"query": """
|
|
80
|
-
SELECT (?parentName AS ?node_name) (?parentClass AS ?node_class) ?parentPath (?parentInst AS ?node_id )
|
|
81
|
-
(?parentInst AS ?src_object_ref) (?parentInst2 AS ?dst_object_ref) WHERE {
|
|
82
|
-
?tagInst a neat:AttributeTag .
|
|
83
|
-
?tagInst neat:Path ?tagPath .
|
|
84
|
-
?tagInst neat:Value ?tagValue .
|
|
85
|
-
?tagInst neat:hasParent+ ?parentInst .
|
|
86
|
-
?parentInst neat:Name ?parentName .
|
|
87
|
-
?parentInst neat:Path ?parentPath .
|
|
88
|
-
?parentInst a ?parentClass .
|
|
89
|
-
?parentInst neat:hasParent ?parentInst2
|
|
90
|
-
} limit 100""",
|
|
91
|
-
},
|
|
92
|
-
]
|
cognite/neat/_app/main.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import webbrowser
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
import uvicorn
|
|
5
|
-
from fastapi import FastAPI
|
|
6
|
-
|
|
7
|
-
app = FastAPI()
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def run():
|
|
11
|
-
(Path.cwd() / "data").mkdir(parents=True, exist_ok=True)
|
|
12
|
-
webbrowser.open("http://localhost:8000/")
|
|
13
|
-
uvicorn.run("cognite.neat._app.api.explorer:app", host="0.0.0.0", port=8000, reload=False)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if __name__ == "__main__":
|
|
17
|
-
run()
|
|
File without changes
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
from collections.abc import Iterable
|
|
2
|
-
from typing import cast
|
|
3
|
-
|
|
4
|
-
from cognite.client import CogniteClient
|
|
5
|
-
from prometheus_client import REGISTRY, Counter, Gauge, Metric
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class NeatMetricsCollector:
|
|
9
|
-
def __init__(self, name: str, cdf_client: CogniteClient | None = None) -> None:
|
|
10
|
-
self.name = name
|
|
11
|
-
self.metrics: dict[str, Gauge | Counter] = {}
|
|
12
|
-
|
|
13
|
-
def register_metric(
|
|
14
|
-
self,
|
|
15
|
-
metric_name: str,
|
|
16
|
-
metric_description: str = "",
|
|
17
|
-
m_type: str = "gauge",
|
|
18
|
-
metric_labels: list[str] | None = None,
|
|
19
|
-
) -> Gauge | Counter | None:
|
|
20
|
-
"""Register metric in prometheus"""
|
|
21
|
-
metric_name = self.sanitize_metric_name(metric_name)
|
|
22
|
-
metric_labels = [] if metric_labels is None else metric_labels
|
|
23
|
-
|
|
24
|
-
metric_name = f"neat_workflow_{self.sanitize_metric_name(self.name)}_{metric_name}"
|
|
25
|
-
if metric_name in REGISTRY._names_to_collectors:
|
|
26
|
-
self.metrics[metric_name] = cast(Gauge | Counter, REGISTRY._names_to_collectors[metric_name])
|
|
27
|
-
return self.metrics[metric_name]
|
|
28
|
-
|
|
29
|
-
metric: Gauge | Counter | None = None
|
|
30
|
-
if m_type == "gauge":
|
|
31
|
-
metric = Gauge(metric_name, metric_description, metric_labels)
|
|
32
|
-
elif m_type == "counter":
|
|
33
|
-
metric = Counter(metric_name, metric_description, metric_labels)
|
|
34
|
-
|
|
35
|
-
if metric:
|
|
36
|
-
self.metrics[metric_name] = metric
|
|
37
|
-
return metric
|
|
38
|
-
return None
|
|
39
|
-
|
|
40
|
-
def get(self, metric_name: str, labels: dict[str, str] | None = None) -> Gauge | Counter | None:
|
|
41
|
-
"""Return metric by name"""
|
|
42
|
-
metric_name = self.sanitize_metric_name(metric_name)
|
|
43
|
-
labels = {} if labels is None else labels
|
|
44
|
-
metric_name = f"neat_workflow_{self.sanitize_metric_name(self.name)}_{metric_name}"
|
|
45
|
-
if metric_name in self.metrics:
|
|
46
|
-
return self.metrics[metric_name].labels(**(labels or {}))
|
|
47
|
-
return None
|
|
48
|
-
|
|
49
|
-
def report_metric_value(
|
|
50
|
-
self,
|
|
51
|
-
metric_name: str,
|
|
52
|
-
metric_description: str = "",
|
|
53
|
-
m_type: str = "gauge",
|
|
54
|
-
labels: dict[str, str] | None = None,
|
|
55
|
-
) -> Gauge | Counter | None:
|
|
56
|
-
self.sanitize_metric_name(metric_name)
|
|
57
|
-
metric = self.register_metric(metric_name, metric_description, m_type, [k for k, v in (labels or {}).items()])
|
|
58
|
-
if metric:
|
|
59
|
-
return metric.labels(**(labels or {}))
|
|
60
|
-
return None
|
|
61
|
-
|
|
62
|
-
def sanitize_metric_name(self, metric_name: str) -> str:
|
|
63
|
-
return metric_name.replace("-", "_").replace(" ", "_").replace(".", "_").replace(":", "_").lower()
|
|
64
|
-
|
|
65
|
-
def collect(self) -> Iterable[Metric]:
|
|
66
|
-
return cast(Iterable[Metric], self.metrics.values())
|
|
67
|
-
|
|
68
|
-
def report_to_cdf(self):
|
|
69
|
-
pass
|
cognite/neat/_app/ui/index.html
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
-
|
|
3
|
-
# dependencies
|
|
4
|
-
/node_modules
|
|
5
|
-
/.pnp
|
|
6
|
-
.pnp.js
|
|
7
|
-
|
|
8
|
-
# testing
|
|
9
|
-
/coverage
|
|
10
|
-
|
|
11
|
-
# production
|
|
12
|
-
# /build
|
|
13
|
-
|
|
14
|
-
# misc
|
|
15
|
-
.DS_Store
|
|
16
|
-
.env.local
|
|
17
|
-
.env.development.local
|
|
18
|
-
.env.test.local
|
|
19
|
-
.env.production.local
|
|
20
|
-
|
|
21
|
-
npm-debug.log*
|
|
22
|
-
yarn-debug.log*
|
|
23
|
-
yarn-error.log*
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# Getting Started with Create React App
|
|
2
|
-
|
|
3
|
-
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
|
4
|
-
|
|
5
|
-
## Available Scripts
|
|
6
|
-
|
|
7
|
-
In the project directory, you can run:
|
|
8
|
-
|
|
9
|
-
### `npm start`
|
|
10
|
-
|
|
11
|
-
Runs the app in the development mode.\
|
|
12
|
-
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
|
13
|
-
|
|
14
|
-
The page will reload when you make changes.\
|
|
15
|
-
You may also see any lint errors in the console.
|
|
16
|
-
|
|
17
|
-
### `npm test`
|
|
18
|
-
|
|
19
|
-
Launches the test runner in the interactive watch mode.\
|
|
20
|
-
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
|
21
|
-
|
|
22
|
-
### `npm run build`
|
|
23
|
-
|
|
24
|
-
Builds the app for production to the `build` folder.\
|
|
25
|
-
It correctly bundles React in production mode and optimizes the build for the best performance.
|
|
26
|
-
|
|
27
|
-
The build is minified and the filenames include the hashes.\
|
|
28
|
-
Your app is ready to be deployed!
|
|
29
|
-
|
|
30
|
-
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
|
31
|
-
|
|
32
|
-
### `npm run eject`
|
|
33
|
-
|
|
34
|
-
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
|
35
|
-
|
|
36
|
-
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
|
37
|
-
|
|
38
|
-
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
|
39
|
-
|
|
40
|
-
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
|
41
|
-
|
|
42
|
-
## Learn More
|
|
43
|
-
|
|
44
|
-
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
|
45
|
-
|
|
46
|
-
To learn React, check out the [React documentation](https://reactjs.org/).
|
|
47
|
-
|
|
48
|
-
### Code Splitting
|
|
49
|
-
|
|
50
|
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
|
51
|
-
|
|
52
|
-
### Analyzing the Bundle Size
|
|
53
|
-
|
|
54
|
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
|
55
|
-
|
|
56
|
-
### Making a Progressive Web App
|
|
57
|
-
|
|
58
|
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
|
59
|
-
|
|
60
|
-
### Advanced Configuration
|
|
61
|
-
|
|
62
|
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
|
63
|
-
|
|
64
|
-
### Deployment
|
|
65
|
-
|
|
66
|
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
|
67
|
-
|
|
68
|
-
### `npm run build` fails to minify
|
|
69
|
-
|
|
70
|
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|