├── .gitignore ├── LICENSE ├── README.md ├── api ├── .env.example ├── .env.example.extrasettings ├── Dockerfile ├── README.md ├── babel.config.json ├── img │ └── graphql-playground.png ├── license.lic ├── license.lic.labs ├── package-lock.json ├── package.json ├── python │ ├── workbench_client.py │ └── workbench_client_test.py.ipynb ├── scripts │ ├── create-user.sh │ ├── make-credential-file.sh │ ├── run-workflow.sh │ ├── test.json │ └── workbench-client.sh └── src │ ├── analyze │ ├── DataTypes.js │ ├── readCsv.js │ ├── tableValueMap.js │ └── tableValueMap.test.js │ ├── api │ └── client │ │ ├── clientGraphQLRequests.js │ │ └── testApiClient.js │ ├── cypher │ ├── cypherGen │ │ └── generateCypher.cypher │ ├── ds_workbench │ │ ├── cypher_constraints.cypher │ │ └── cypher_init_data.cypher │ └── systemMessages.cypher │ ├── data │ └── neo4jtobigquery.js │ ├── debug │ ├── DataModelMetadataTransform.js │ ├── DataModelMetadataTransform.test.js │ └── debug.js │ ├── index.js │ ├── layout │ ├── colors.js │ └── layout.js │ ├── license │ ├── license.js │ └── licenseKeyConstants.js │ ├── models │ ├── bigQuery │ │ ├── bigQuery.js │ │ └── bigQuery.mjs │ ├── cypherAssociationCypherStatements.js │ ├── cypherAssociations.js │ ├── cypherStatements.js │ ├── dataModelPersistenceHelper.js │ ├── dataTransfer.js │ ├── datamodel.js │ ├── dbConnection.js │ ├── graphDoc.js │ ├── graphDocCypherStatements.js │ ├── phase.js │ ├── resultHelper.js │ ├── searchGraphDocCypher.js │ ├── searchModelCypher.js │ ├── userRoles.js │ ├── userRolesCypher.js │ └── users.js │ ├── resolvers │ ├── cypherAssociations.js │ ├── dataTransfer.js │ ├── datamodel.js │ ├── dbConnection.js │ ├── graphDoc.js │ ├── json.js │ ├── phase.js │ ├── userRoles.js │ └── users.js │ ├── security │ └── localSessions.js │ ├── types │ ├── cypherAssociations.js │ ├── dataTransfer.js │ ├── datamodel.js │ ├── dbConnection.js │ ├── graphDoc.js │ ├── json.js │ ├── phase.js │ ├── userRoles.js │ └── users.js │ ├── ui │ ├── common │ │ └── parse │ │ │ ├── antlr │ │ │ ├── AutocompleteErrorListener.js │ │ │ ├── Cypher.g4 │ │ │ ├── Cypher.interp │ │ │ ├── Cypher.tokens │ │ │ ├── CypherDataModelTests.test.js │ │ │ ├── CypherLexer.interp │ │ │ ├── CypherLexer.js │ │ │ ├── CypherLexer.tokens │ │ │ ├── CypherListener.js │ │ │ ├── CypherParser.js │ │ │ ├── CypherStatement.js │ │ │ ├── CypherStatementBuilderListener.js │ │ │ ├── CypherTests.test.js │ │ │ ├── ErrorListener.js │ │ │ ├── GameOfThronesParseTest.test.js │ │ │ ├── WithClauseListener.js │ │ │ ├── antlr-4.10.1-complete.jar │ │ │ ├── parseReadme.txt │ │ │ └── withVariable.js │ │ │ └── parseCypher.js │ ├── components │ │ └── canvas │ │ │ └── d3 │ │ │ ├── grid.js │ │ │ └── helpers.js │ ├── dataModel │ │ ├── DataTypes.js │ │ ├── JaroWrinker.js │ │ ├── dataModel.js │ │ ├── dataModelExtension.js │ │ └── helper.js │ └── syncSrcFiles.sh │ ├── users │ ├── createUser.js │ ├── decryptV1.js │ └── encryptV1.js │ ├── util │ ├── db.js │ ├── encryption.js │ └── run.js │ └── version.js ├── docker ├── README.md ├── api │ ├── Dockerfile.api │ ├── build_labs.sh │ └── cleanup_api.sh ├── build_labs.sh ├── cw-config │ ├── cw-config │ │ ├── cw-api │ │ │ └── license.lic │ │ ├── cw-certificate │ │ │ ├── workbench.crt │ │ │ └── workbench.key │ │ ├── cw-db-setup │ │ │ ├── cypher_constraints_v4.3.cypher │ │ │ ├── cypher_constraints_v4.4_to_5.cypher │ │ │ └── cypher_init_data.cypher │ │ ├── cw-ui-nginx-template │ │ │ └── nginx.conf.template │ │ ├── cw-ui-nginx │ │ │ ├── dhparam.pem │ │ │ ├── workbench-ssl-params.conf │ │ │ ├── workbench.crt │ │ │ └── workbench.key │ │ └── cw-ui-template │ │ │ └── env-config.js.template │ └── scripts │ │ ├── create-user.sh │ │ └── make-credential-file.sh ├── getVersion.sh ├── ui │ ├── Dockerfile.ui │ ├── build_labs.sh │ └── cleanup_ui.sh └── workbench_labs_files │ ├── certficate │ ├── dhparam.pem │ ├── workbench.crt │ └── workbench.key │ ├── docker-compose-labs.yml │ ├── envConfig.sh │ ├── makeEnv.sh │ ├── makeEnvConfig.sh │ ├── makeNginxConf.sh │ └── workbench_labs ├── find_modified_files.py └── ui ├── .env.example ├── .env.example.extrasettings ├── Dockerfile ├── README.md ├── nginx.conf ├── package-lock.json ├── package.json ├── public ├── boltWrapper.html ├── config │ └── env-config.js ├── favicon.ico ├── favicon.png ├── favicon_react.ico ├── index.html ├── js │ └── boltProxy.js ├── logo192.png ├── logo512.png ├── manifest.json ├── neo4j-logo-white-RGB-transBG.png └── robots.txt ├── src ├── App.css ├── App.js ├── App.test.js ├── auth │ ├── auth0.js │ ├── authUtil.js │ ├── callback.js │ ├── history.js │ ├── localAuth.js │ └── privateRoute.js ├── common │ ├── Constants.js │ ├── Cypher.js │ ├── LicensedFeatures.js │ ├── LicensedFeatures.test.js │ ├── LicensedFeaturesCheck.js │ ├── Wrapper.js │ ├── css │ │ └── react-resizable.css │ ├── encryption.js │ ├── encryption.test.js │ ├── eula │ │ ├── Email.js │ │ ├── Eula.js │ │ ├── EulaOnline.js │ │ └── eulaHelper.js │ ├── layout │ │ └── layout.js │ ├── messages │ │ └── SystemMessages.js │ ├── parse │ │ ├── allFunctions.js │ │ ├── antlr │ │ │ ├── .antlr │ │ │ │ ├── Cypher.interp │ │ │ │ ├── Cypher.tokens │ │ │ │ ├── CypherBaseListener.java │ │ │ │ ├── CypherLexer.interp │ │ │ │ ├── CypherLexer.java │ │ │ │ ├── CypherLexer.tokens │ │ │ │ ├── CypherListener.java │ │ │ │ ├── CypherParser.java │ │ │ │ ├── Cypher_prev.interp │ │ │ │ ├── Cypher_prev.tokens │ │ │ │ ├── Cypher_prevBaseListener.java │ │ │ │ ├── Cypher_prevLexer.interp │ │ │ │ ├── Cypher_prevLexer.java │ │ │ │ ├── Cypher_prevLexer.tokens │ │ │ │ ├── Cypher_prevListener.java │ │ │ │ ├── Cypher_prevParser.java │ │ │ │ ├── Cypher_update.interp │ │ │ │ ├── Cypher_update.tokens │ │ │ │ ├── Cypher_updateLexer.interp │ │ │ │ ├── Cypher_updateLexer.java │ │ │ │ ├── Cypher_updateLexer.tokens │ │ │ │ └── Cypher_updateParser.java │ │ │ ├── AutocompleteErrorListener.js │ │ │ ├── Cypher.g4 │ │ │ ├── Cypher.interp │ │ │ ├── Cypher.tokens │ │ │ ├── CypherDataModelTests.test.js │ │ │ ├── CypherLexer.interp │ │ │ ├── CypherLexer.js │ │ │ ├── CypherLexer.tokens │ │ │ ├── CypherListener.js │ │ │ ├── CypherParser.js │ │ │ ├── CypherStatement.js │ │ │ ├── CypherStatementBuilderListener.js │ │ │ ├── CypherTests.test.js │ │ │ ├── ErrorListener.js │ │ │ ├── GameOfThronesParseTest.test.js │ │ │ ├── LargeCypherQueriesParseTest.test.js │ │ │ ├── WithClauseListener.js │ │ │ ├── antlr-4.10.1-complete.jar │ │ │ ├── commentUtil.js │ │ │ ├── commentUtil.test.js │ │ │ ├── parseReadme.txt │ │ │ ├── stringIndexFinder.js │ │ │ ├── stringIndexFinder.test.js │ │ │ └── withVariable.js │ │ ├── autocompleteHelper.js │ │ ├── autocompleteHelper.test.js │ │ ├── cypherFunctionHelper.js │ │ ├── cypherFunctionHelper.test.js │ │ ├── cypherKeywords.js │ │ ├── cypherTemplate.js │ │ ├── cypherTemplate.test.js │ │ ├── cypherTemplateSecurity.test.js │ │ ├── parseCypher.js │ │ ├── parseCypher.test.js │ │ ├── parseUtil.js │ │ ├── parseWith.js │ │ ├── parseWith.test.js │ │ ├── unitTest.js │ │ └── unitTest.test.js │ ├── test │ │ └── testHelper.js │ ├── text │ │ ├── textUtil.js │ │ └── textUtil.test.js │ ├── transform │ │ ├── DataModelMetadataTransform.js │ │ └── DataModelMetadataTransform.test.js │ └── util │ │ ├── download.js │ │ ├── timeUtil.test.js │ │ ├── timerUtil.js │ │ └── tracking.js ├── components │ ├── canvas │ │ ├── Canvas.js │ │ ├── CanvasDataProviderInterface.js │ │ ├── CanvasSearch.js │ │ ├── ColorCircles.js │ │ ├── ColorPicker.js │ │ ├── GraphCanvasControl.js │ │ ├── GraphCanvasRole.js │ │ ├── GraphCanvasSearch.js │ │ ├── SizeCircles.js │ │ ├── ZoomControls.js │ │ └── d3 │ │ │ ├── annotation.js │ │ │ ├── annotationPlacementHelper.js │ │ │ ├── canvasConfigDefaultConstants.js │ │ │ ├── canvasMath.js │ │ │ ├── dataModelCanvas.js │ │ │ ├── glyphs.js │ │ │ ├── graphCanvas.js │ │ │ ├── graphCanvasAnnotation.js │ │ │ ├── graphCanvasGlyph.js │ │ │ ├── graphCanvasHelpers.js │ │ │ ├── grid.js │ │ │ ├── helpers.js │ │ │ ├── layoutHelper.js │ │ │ ├── nodeArcs.js │ │ │ ├── relationshipRibbon.js │ │ │ ├── selectionHelper.js │ │ │ ├── textPositionHelper.js │ │ │ ├── textWrapHelper.js │ │ │ └── zoomHelper.js │ ├── common │ │ ├── ComboBox.js │ │ ├── Components.js │ │ ├── CustomChip.js │ │ ├── CustomChips.js │ │ ├── ExportDataImporterDialog.js │ │ ├── FullScreenWaitOverlay.js │ │ ├── GeneralDialog.js │ │ ├── GeneralSelectDialog.js │ │ ├── GeneralTextDialog.js │ │ ├── GeneralTextDialogWithOptions.js │ │ ├── ModalChips.js │ │ ├── ModalRelationship.js │ │ ├── ModalVariableLabelsAndTypes.js │ │ ├── MultiTextField.js │ │ ├── SearchableChips.js │ │ └── VariableLabelsAndTypes.js │ └── menu │ │ ├── CheckmarkMenuItem.js │ │ ├── EnhancedMenu.js │ │ ├── Menu.js │ │ ├── StatefulCheckmarkMenuItem.js │ │ └── SubMenu.js ├── dataModel │ ├── DataTypes.js │ ├── JaroWrinker.js │ ├── cypherClause.js │ ├── cypherClause.test.js │ ├── cypherLimit.js │ ├── cypherOrderBy.js │ ├── cypherOrderBy.test.js │ ├── cypherPattern.js │ ├── cypherPattern.test.js │ ├── cypherPatternDebug.test.js │ ├── cypherPatternValidate.test.js │ ├── cypherReturn.js │ ├── cypherReturn.test.js │ ├── cypherSkip.js │ ├── cypherSnippetSet.js │ ├── cypherStatementBuilder.js │ ├── cypherStatementBuilder.test.js │ ├── cypherStringConverter.js │ ├── cypherStringConverter.test.js │ ├── cypherSubQuery.js │ ├── cypherSubQuery.test.js │ ├── cypherUnion.js │ ├── cypherUnion.test.js │ ├── cypherVariableScope.js │ ├── cypherVariableScope.test.js │ ├── cypherWhere.js │ ├── cypherWhere.test.js │ ├── dataModel.js │ ├── dataModel.test.js │ ├── dataModelDiff.js │ ├── dataModelDiff.test.js │ ├── dataModelExtension.basic.js │ ├── dataModelExtension.enterprise.js │ ├── dataModelExtension.js │ ├── dataSource │ │ ├── cypherDataInstructions.js │ │ ├── cypherDataSource.js │ │ ├── cypherDataSource.test.js │ │ ├── cypherTextOutput.js │ │ ├── cypherTextOutput.test.js │ │ ├── dataMapping.js │ │ ├── generalDataSource.js │ │ ├── generalDataSource.test.js │ │ ├── tableData.js │ │ ├── testHelper.js │ │ ├── transform.js │ │ ├── transform.test.js │ │ └── transformationStep.js │ ├── dataValidation.js │ ├── debugStatement.test.js │ ├── debugSteps.js │ ├── debugSteps.test.js │ ├── debugVariableUtil.js │ ├── debugVariableUtil.test.js │ ├── eventEmitter.js │ ├── eventEmitter.test.js │ ├── explanations.js │ ├── graphData.js │ ├── graphData.test.js │ ├── graphDataConstants.js │ ├── graphDataView.js │ ├── graphDataView.test.js │ ├── graphModel │ │ ├── graphModel.test.ts │ │ ├── graphModel.ts │ │ ├── workbenchDataModelToImporterGraphModel.test.ts │ │ └── workbenchDataModelToImporterGraphModel.ts │ ├── graphUtil.js │ ├── graphUtil.test.js │ ├── helper.js │ ├── indexes │ │ ├── v4 │ │ │ └── indexSyntax.cypher │ │ └── v5 │ │ │ └── indexSyntax.cypher │ ├── keyHelper.js │ ├── keyHelper.test.js │ ├── neo4jdb │ │ ├── constraintIndexTypes.js │ │ ├── constraints.js │ │ ├── constraints.test.js │ │ ├── dataModelHelper.js │ │ ├── indexes.js │ │ └── indexes.test.js │ ├── propertyUtil │ │ ├── propertyAnalyzer.js │ │ └── propertyAnalyzer.test.js │ ├── syncedGraphDataAndView.js │ ├── syncedGraphDataAndView.test.js │ ├── testUtil │ │ └── testUtil.js │ ├── validation │ │ ├── dataModelValidation.js │ │ └── dataModelValidation.test.js │ └── vsCodeDebugHelper.js ├── dynamicConfig.js ├── homePage │ ├── components │ │ ├── NeoConnectionForm.js │ │ └── SettingsForm.js │ └── index.js ├── index.css ├── index.js ├── logo.svg ├── persistence │ ├── LocalPersistence.js │ └── graphql │ │ ├── GraphQLAdminApp.js │ │ ├── GraphQLCypherAssociations.js │ │ ├── GraphQLDBConnection.js │ │ ├── GraphQLDataTransfer.js │ │ ├── GraphQLGraphDoc.js │ │ ├── GraphQLKeymaker.js │ │ ├── GraphQLPersistence.js │ │ ├── authGraphql.js │ │ ├── dbConnection.js │ │ ├── getBrandingInfo.js │ │ └── user.js ├── serviceWorker.js ├── tools │ ├── common │ │ ├── DocumentSecurityRole.js │ │ ├── SecurityRole.js │ │ ├── WorkStatus.js │ │ ├── blocks │ │ │ ├── AccordionBlock.js │ │ │ └── AccordionBlockNoDrag.js │ │ ├── communicationHelper.js │ │ ├── cypher │ │ │ ├── SelectCypherStatement.js │ │ │ └── processCypher.js │ │ ├── database │ │ │ └── SelectNeo4jDatabase.js │ │ ├── edit │ │ │ ├── LoadForm.js │ │ │ ├── SaveForm.js │ │ │ ├── editHelper.js │ │ │ └── showMoreLessHelper.js │ │ ├── execute │ │ │ └── executeCypher.js │ │ ├── graphCanvas │ │ │ ├── canvasConfig.js │ │ │ └── graphCanvasEditHelper.js │ │ ├── lockHelper.js │ │ ├── mapping │ │ │ ├── DataMappingBlock.js │ │ │ ├── DataSource.js │ │ │ ├── DataSourceTableMapping.js │ │ │ ├── PipelineOptions.js │ │ │ ├── ResultExportDialog.js │ │ │ ├── bigquery │ │ │ │ ├── BigQueryDestination.js │ │ │ │ ├── BigQuerySource.js │ │ │ │ └── BigQueryUtil.js │ │ │ ├── dataSourceTableBlock.js │ │ │ └── neo4j │ │ │ │ ├── Neo4jSource.js │ │ │ │ ├── dataExportJson.js │ │ │ │ └── exportNeoToNeo.js │ │ ├── model │ │ │ ├── loadModelDialogHelper.js │ │ │ └── modelPersistence.js │ │ ├── persistenceHelper.js │ │ ├── security │ │ │ ├── Sharing.js │ │ │ ├── UserRole.js │ │ │ ├── UserRoleDropDown.js │ │ │ └── UserRoles.js │ │ ├── toolConstants.js │ │ ├── util.js │ │ └── validation │ │ │ ├── CypherValidation.js │ │ │ ├── ValidationSection.js │ │ │ ├── ValidationStatus.js │ │ │ └── ValidationUtil.js │ ├── toolCypherBuilder │ │ ├── CypherBuilderRefactor.js │ │ └── components │ │ │ ├── ActiveCypherQuery.js │ │ │ ├── AddControl.js │ │ │ ├── CypherQueryList.js │ │ │ ├── CypherUxControl.js │ │ │ ├── cypherBlocks │ │ │ ├── CypherAccordionBlock.js │ │ │ ├── CypherPatternBlock.js │ │ │ ├── DataModelUtil.js │ │ │ ├── OrderByEditHelper.js │ │ │ ├── ReturnEditHelper.js │ │ │ ├── ReturnOrOrderByClauseBlock.js │ │ │ ├── ReturnOrOrderByItems.js │ │ │ ├── SkipOrLimitClauseBlock.js │ │ │ ├── SlateCypherEditorBlock.js │ │ │ ├── WhereClauseBlock.js │ │ │ ├── WhereEditHelper.js │ │ │ ├── WhereItem.js │ │ │ └── WhereItems.js │ │ │ ├── dataProvider │ │ │ ├── blockHelper.js │ │ │ ├── blockHelper.test.js │ │ │ ├── blockUpdateTypes.js │ │ │ ├── cypherBlock.js │ │ │ ├── cypherBlockDataProvider.js │ │ │ ├── cypherBlockDataProvider.test.js │ │ │ ├── cypherPatternCanvasDataProvider.test.js │ │ │ ├── cypherPatternCanvasDataProviderRefactor.js │ │ │ ├── cypherSnippetDataProvider.js │ │ │ ├── dataMappingDataProvider.js │ │ │ ├── dataMappingJson.js │ │ │ ├── dataModelCanvasDataProvider.js │ │ │ ├── limitClauseDataProvider.js │ │ │ ├── orderByClauseDataProvider.js │ │ │ ├── returnClauseDataProvider.js │ │ │ ├── scopedBlockProvider.js │ │ │ ├── skipClauseDataProvider.js │ │ │ └── whereClauseDataProvider.js │ │ │ ├── graphCanvas │ │ │ └── dataModelCanvasConfig.js │ │ │ ├── results │ │ │ └── ResultsTable.js │ │ │ └── validation │ │ │ └── ValidationTable.js │ ├── toolCypherDebug │ │ ├── CypherDebug.js │ │ ├── components │ │ │ ├── BreakpointExtension.js │ │ │ ├── Capsules.js │ │ │ ├── Constants.js │ │ │ ├── DebugCanvasPlainNvl.js │ │ │ ├── DebugCanvasPlainNvlSample.js │ │ │ ├── DebugToolbar.js │ │ │ ├── GraphSummaryAndConfig.js │ │ │ ├── LayoutControls.js │ │ │ ├── MiniResultsTable.js │ │ │ ├── MiniResultsTableToolbar.css │ │ │ ├── MiniResultsTableToolbar.js │ │ │ └── divider.js │ │ ├── database │ │ │ └── databaseUtil.js │ │ ├── export │ │ │ ├── runAndExportData.js │ │ │ └── runAndExportData.test.js │ │ └── validation │ │ │ └── validateCypher.js │ ├── toolCypherSuite │ │ ├── CypherSuite.js │ │ └── components │ │ │ ├── CypherValidation.js │ │ │ ├── cypherBlocks │ │ │ └── SlateCypherEditorBlock.js │ │ │ ├── dataProvider │ │ │ ├── cypherBlock.js │ │ │ ├── cypherBlockDataProvider.js │ │ │ ├── cypherDataProvider.js │ │ │ └── graphDebugDataProvider.js │ │ │ ├── exportResults.js │ │ │ ├── exportResults.test.js │ │ │ └── graphCanvas │ │ │ └── graphDebugCanvasConfig.js │ ├── toolDashboard │ │ ├── Dashboard.js │ │ └── components │ │ │ ├── common │ │ │ └── DashboardCard.js │ │ │ └── dataProvider │ │ │ ├── dashboardCard.js │ │ │ ├── dashboardCardDataProvider.js │ │ │ └── dashboardDataProvider.js │ ├── toolDataMapping │ │ ├── DataMapping.js │ │ └── dataProvider │ │ │ ├── dataMappingDataProvider.js │ │ │ ├── dataSourceTableMappingDataProvider.js │ │ │ └── updateTypes.js │ ├── toolDataScienceDashboard │ │ ├── DataScienceDashboard.js │ │ ├── DataScienceTable.js │ │ ├── DataScienceTabs.js │ │ └── JobDataProcessing.js │ ├── toolDatabases │ │ ├── Databases.js │ │ ├── KeyMakerApp.css │ │ ├── components │ │ │ ├── CreateDatabaseConnectionModal.js │ │ │ ├── DatabaseConnectionCard.js │ │ │ ├── DatabaseGrid.js │ │ │ ├── DatabaseSharing.js │ │ │ ├── DatabaseUserRole.js │ │ │ ├── DatabaseUserRoles.js │ │ │ └── EditDatabaseConnectionModal.js │ │ ├── keyMakerComponents │ │ │ ├── Button.js │ │ │ ├── Card.js │ │ │ ├── DeleteModal.js │ │ │ ├── FormModal.js │ │ │ └── MessageModal.js │ │ └── util │ │ │ └── helper.js │ ├── toolModel │ │ ├── Model.js │ │ ├── components │ │ │ ├── DataExport.js │ │ │ ├── LoadModelForm.js │ │ │ ├── ModelDatabaseValidationCypher.js │ │ │ ├── ModelValidation.js │ │ │ ├── ModelValidationAgainstDatabase.js │ │ │ ├── ModelValidationAgainstDatabase.test.js │ │ │ ├── SaveModelForm.js │ │ │ ├── cardinality │ │ │ │ ├── RelationshipCardinalityBlock.js │ │ │ │ ├── RelationshipCardinalityDialog.js │ │ │ │ └── RelationshipCardinalityLine.js │ │ │ ├── layout │ │ │ │ ├── ModelLayoutSettings.js │ │ │ │ ├── RelationshipLayoutSettings.js │ │ │ │ └── SecondaryNodeLabelSettings.js │ │ │ └── properties │ │ │ │ ├── PropertyDefinition.js │ │ │ │ ├── PropertyDefinition.test.js │ │ │ │ ├── PropertyDefinitionConstraints.js │ │ │ │ ├── PropertyDefinitions.js │ │ │ │ ├── PropertyDefinitions.test.js │ │ │ │ ├── PropertyDialog.js │ │ │ │ └── PropertyIndexes.js │ │ ├── importexport │ │ │ ├── convert.js │ │ │ ├── exportMarkdown.js │ │ │ ├── importApocMeta.js │ │ │ ├── importApocMeta.test.js │ │ │ ├── importArrows.js │ │ │ ├── importArrows.test.js │ │ │ ├── importCypherStatements.js │ │ │ ├── importDbSchema.js │ │ │ ├── importDbSchema.test.js │ │ │ ├── importFromDatabase.js │ │ │ └── importFromDatabase.test.js │ │ └── parse │ │ │ ├── parseModelInput.js │ │ │ └── parseModelInput.test.js │ ├── toolProjects │ │ ├── Projects.js │ │ └── components │ │ │ └── dataProvider │ │ │ └── projectDataProvider.js │ └── toolScenarios │ │ ├── Scenarios.js │ │ └── components │ │ ├── dataProvider │ │ ├── scenarioBlock.js │ │ ├── scenarioBlockDataProvider.js │ │ └── scenarioDataProvider.js │ │ └── scenarioBlocks │ │ ├── AssociatedCypherDisplay.js │ │ ├── ScenarioBlockDisplay.js │ │ └── SlateScenarioEditorBlock.js └── version.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 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 | *.tar.gz 15 | 16 | # misc 17 | .DS_Store 18 | *.env 19 | *.env.development* 20 | *.env.my.development* 21 | *.env.test* 22 | *.env.production* 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | yarn.lock 28 | 29 | *.iml 30 | 31 | .idea/ 32 | api/dist/ 33 | api/.compiled 34 | databases/ 35 | docker/api/api/ 36 | docker/api/keep/ 37 | docker/ui/ui/ 38 | docker/ui/keep/ 39 | docker/cypher_workbench/ 40 | docker/database/ 41 | docker/seedDatabase/ 42 | docker/test/ 43 | docker/dist/ 44 | docker/ds_build/ 45 | docker/build/ 46 | api/datasources/ 47 | -------------------------------------------------------------------------------- /api/.env.example: -------------------------------------------------------------------------------- 1 | # set these values to the Neo4j database instance you've defined for Cypher Workbench 2 | NEO4J_URI=bolt://localhost:7687 3 | NEO4J_USER=neo4j 4 | NEO4J_PASSWORD=password 5 | NEO4J_DATABASE=neo4j 6 | 7 | # if hosting on a server, change this value to the server's hostname 8 | HOST_NAME=localhost 9 | 10 | # port this server listens on 11 | GRAPHQL_LISTEN_PORT=4000 12 | 13 | # protocol this server listens on 14 | HOST_PROTOCOL=http 15 | 16 | # IMPORTANT: change this value before creating any users 17 | # make sure this matches the value of REACT_APP_ENCRYPTION_KEY in ui/.env 18 | ENCRYPTION_KEY=workbenchEncryptionKey 19 | 20 | # Keep the AUTH_METHOD set to local unless you setup your own Auth0 tenant 21 | AUTH_METHOD=local 22 | #AUTH_METHOD=auth0 23 | 24 | # legacy parameter. It's recommended you don't change this value. If you do secure your Neo4j 25 | # instance, change the protocol to bolt+s or neo4j+s in the NEO4J_URI above 26 | NEO4J_ENCRYPTED=ENCRYPTION_OFF 27 | 28 | # if set to true, enables the DefaultPublic SecurityOrganization to accept new users 29 | # when AUTH_METHOD is auth0. Otherwise, only pre-defined EmailDomain email domains 30 | # are allowed 31 | ALLOW_DEFAULT_PUBLIC_SELF_REGISTRATION=false 32 | 33 | # set these to true to turn on Apollo introspection and the GraphQL playground 34 | APOLLO_SERVER_INTROSPECTION=false 35 | APOLLO_SERVER_PLAYGROUND=false 36 | 37 | 38 | -------------------------------------------------------------------------------- /api/.env.example.extrasettings: -------------------------------------------------------------------------------- 1 | 2 | # Not needed unless you configure Authentication via Auth0 3 | #JWKS_URI=https://.auth0.com/.well-known/jwks.json 4 | #CLIENT_ID= 5 | #ISSUER=https://.auth0.com/ 6 | #ALGO=RS256 7 | 8 | # not need unless you have Auth0 setup and want to test the API 9 | #AUTH0_API_KEY_CLIENT_ID= 10 | #AUTH0_API_KEY_CLIENT_SECRET= 11 | #AUTH0_URL=https://.auth0.com/oauth/token 12 | #API_URI=http://:4000/graphql 13 | #TEST_EMAIL=first.last@company.com 14 | 15 | # needed for API and API test program 16 | #AUTH0_API_KEY_AUDIENCE=https:// 17 | #SEGMENT_WRITE_KEY= 18 | 19 | # not needed unless old Keymaker integration setup 20 | # KEYMAKER_NEO4J_URI=bolt://:7687 21 | # KEYMAKER_NEO4J_USER=username 22 | # KEYMAKER_NEO4J_PASSWORD=password 23 | # KEYMAKER_NEO4J_DATABASE=default 24 | 25 | # not needed unless old Data Science workbench integration setup 26 | # HOP_HOST_URL=http:// 27 | # HOP_HTTP_AUTHORIZATION_USER=username 28 | # HOP_HTTP_AUTHORIZATION_PASSWORD=password -------------------------------------------------------------------------------- /api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | RUN mkdir -p /app 4 | WORKDIR /app 5 | 6 | COPY package.json . 7 | COPY license.lic . 8 | RUN npm install 9 | COPY . . 10 | 11 | EXPOSE 4000 12 | 13 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # Cypher Workbench API 2 | The GraphQL and Cypher API code for Cypher Workbench. 3 | 4 | ## Install Dependencies 5 | 6 | Install dependencies: 7 | 8 | Run: 9 | ``` 10 | npm install 11 | ``` 12 | 13 | ## Configure the .env file 14 | Create a `.env` file by copying the `.env.example` file to `.env`. For the Neo4j database you created, set your Neo4j connection string and credentials in `.env`. For example: 15 | 16 | *.env* 17 | 18 | ``` 19 | NEO4J_URI=bolt://localhost:7687 20 | NEO4J_USER=neo4j 21 | NEO4J_PASSWORD=password 22 | NEO4J_DATABASE=neo4j 23 | ``` 24 | 25 | ## Start the API 26 | 27 | Start the service: 28 | 29 | ``` 30 | npm start 31 | ``` 32 | 33 | This will start the Workbench GraphQL service (by default on localhost:4000) that is required by the UI. 34 | 35 | For more instructions see the README file in the parent directory. 36 | 37 | -------------------------------------------------------------------------------- /api/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "targets": { 7 | "edge": "17", 8 | "firefox": "60", 9 | "chrome": "67", 10 | "safari": "11.1", 11 | }, 12 | "useBuiltIns": "usage", 13 | "corejs": "3.6.4", 14 | } 15 | ] 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /api/img/graphql-playground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo4j-labs/cypher-workbench/1689899a882edc793d30603fbb31fcb0ce1e63c4/api/img/graphql-playground.png -------------------------------------------------------------------------------- /api/license.lic: -------------------------------------------------------------------------------- 1 | ==== Cypher Workbench 1.X - Labs ==== i8+LfNcu63uJdCyqc726o4dongCE3UdwtUiS5e7ZAxsUJASPjMjRKZHIzh8POZOKdDu6JvzsJzhwxQLwMjmjxDCE+3ds+dxxOLndBWsV1OdwI71sr7XameeONz6PGONTRWNeRw49pexdQWY0ecK+AA8/r/yA0amq6ZzqGfj6HlxXw6TiGE8KyE1DC+o/jj+7MnBIvO7eUF1xdGPtWEdCP5p7tm+5YQBerTrvT7deWzNLtrTYqgELlM7+KG5MSoAtfEiyyWzQp5v336L6vF2SIKMgbOC7Wbpp9DgCXy4mBBsf0bodzFU3vy6TrrhHDfbzRl1cXD+QuQuU05cXG/QAURxFT5ElgA3MX604a0mBCqTb4btCoCW6RpTeSAhJcEFZXfh/3Ld4GmN2ZoKcM7IALoLzrCy5N95VI+7TTXoCo+lkj8oRKpU6pLir2WVUJmnckYyGCvpmNuI/2WP6eTr8N9N6MN+mXw5Zj6nDR3WsKZTKtW1egCInc6g5E8qQW/5LqOCXr9lDP+PrMA6mN/mj90bpMnc=_$$_byYsLlj9+0QDc48jLVN77cKSAHiECAVplUdfYC+xeIeHmPbIDHSmeNKEPfajxYref33PrruSi6F8eMK3XmErBA== -------------------------------------------------------------------------------- /api/license.lic.labs: -------------------------------------------------------------------------------- 1 | ==== Cypher Workbench 1.X - Labs ==== i8+LfNcu63uJdCyqc726o4dongCE3UdwtUiS5e7ZAxsUJASPjMjRKZHIzh8POZOKdDu6JvzsJzhwxQLwMjmjxDCE+3ds+dxxOLndBWsV1OdwI71sr7XameeONz6PGONTRWNeRw49pexdQWY0ecK+AA8/r/yA0amq6ZzqGfj6HlxXw6TiGE8KyE1DC+o/jj+7MnBIvO7eUF1xdGPtWEdCP5p7tm+5YQBerTrvT7deWzNLtrTYqgELlM7+KG5MSoAtfEiyyWzQp5v336L6vF2SIKMgbOC7Wbpp9DgCXy4mBBsf0bodzFU3vy6TrrhHDfbzRl1cXD+QuQuU05cXG/QAURxFT5ElgA3MX604a0mBCqTb4btCoCW6RpTeSAhJcEFZXfh/3Ld4GmN2ZoKcM7IALoLzrCy5N95VI+7TTXoCo+lkj8oRKpU6pLir2WVUJmnckYyGCvpmNuI/2WP6eTr8N9N6MN+mXw5Zj6nDR3WsKZTKtW1egCInc6g5E8qQW/5LqOCXr9lDP+PrMA6mN/mj90bpMnc=_$$_byYsLlj9+0QDc48jLVN77cKSAHiECAVplUdfYC+xeIeHmPbIDHSmeNKEPfajxYref33PrruSi6F8eMK3XmErBA== -------------------------------------------------------------------------------- /api/scripts/create-user.sh: -------------------------------------------------------------------------------- 1 | PRIMARY_ORGANIZATION=$2 2 | USERID=$3 3 | PASSWORD=$4 4 | NAME=$5 5 | PICTURE=$6 6 | echo "Creating $USERID" 7 | if [ "$#" -lt 5 ]; 8 | then 9 | echo 'Usage: ./create-user.sh [picture] [host] [graphql port] [https]' 10 | exit 1 11 | fi 12 | 13 | HOST=localhost 14 | if [[ -n "$7" ]]; then HOST=$7; fi 15 | 16 | #PORT=37400 17 | PORT=80 18 | if [[ -n "$8" ]]; then PORT=$8; fi 19 | 20 | PROTOCOL=http 21 | if [[ -n "$9" ]]; then PROTOCOL=$9; fi 22 | 23 | TYPE='"type": "Basic"' 24 | CREDENTIALS='"credentials": "'$(<$1)'"' 25 | CREDENTIALS_JSON="{ $TYPE, $CREDENTIALS}" 26 | #echo $CREDENTIALS_JSON 27 | 28 | curl "$PROTOCOL://$HOST:$PORT/graphql" \ 29 | -H 'content-type: application/json' \ 30 | -H "authorization: $CREDENTIALS_JSON" \ 31 | --data '{"query":"mutation {\n createUserSignUp(input: {primaryOrganization: \"'"$PRIMARY_ORGANIZATION"'\", email: \"'"$USERID"'\", password: \"'"$PASSWORD"'\", name: \"'"$NAME"'\", picture: \"'"$PICTURE"'\"}) {\n email\n name\n picture\n }\n}\n"}' \ 32 | --compressed 33 | -------------------------------------------------------------------------------- /api/scripts/make-credential-file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ "$#" -lt 3 ]] ; then 3 | echo "Usage: ./make_credential_file " 4 | exit 2 5 | fi 6 | CREDENTIALS=$1:$2 7 | ENCODED_CREDENTIALS=$(echo -n $CREDENTIALS | base64) 8 | echo $ENCODED_CREDENTIALS > $3 9 | cat $3 10 | -------------------------------------------------------------------------------- /api/scripts/run-workflow.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # https://stedolan.github.io/jq/ 3 | if [[ "$#" -lt 2 ]] ; then 4 | echo "Usage: ./run-workflow [protocol] [host] [port]" 5 | exit 2 6 | fi 7 | if [[ -f "$1" ]] ; then 8 | echo "Found mapping data file, $1" 9 | else 10 | echo "Unable to find mapping data file, $1" 11 | exit 2 12 | fi 13 | USER=$2 14 | PROTOCOL=http 15 | if [[ -n "$3" ]] ; then PROTOCOL=$3; fi 16 | HOST=localhost 17 | if [[ -n "$4" ]] ; then HOST=$4; fi 18 | PORT=4000 19 | if [[ -n "$5" ]] ; then PORT=$5; fi 20 | # can use python to validate that file has json contents 21 | # python -mjson.tool "$1" 22 | # read contents of file and then format for json-string param in query 23 | RAWMAP=$(<$1) 24 | CLEANMAP=$(echo "$RAWMAP" | sed 's/"/\\\\\\"/g') 25 | # build query for graphql 26 | SERVICEID="bigquery-neo4j-incremental" 27 | RWFMUTATION='{"query":"mutation RunWorkFlow{\n runWorkflow(serviceId:\"'"$SERVICEID"'\", \n mappingData: \"'"$CLEANMAP"'\")}"}' 28 | echo "Running Workflow" 29 | curl "$PROTOCOL://$HOST:$PORT/graphql" \ 30 | -H 'Accept-Encoding: gzip, deflate, br' \ 31 | -H 'Content-Type: application/json' \ 32 | -H 'Accept: application/json' \ 33 | -H 'Connection: keep-alive' \ 34 | -H 'DNT: 1' -H "Origin: $PROTOCOL://$HOST:$PORT" \ 35 | -H "authorization: Bearer $USER" \ 36 | --data-binary "$RWFMUTATION" \ 37 | --compressed -------------------------------------------------------------------------------- /api/scripts/test.json: -------------------------------------------------------------------------------- 1 | {"test": {"a":1, "b":2, "c": "foo"}} 2 | -------------------------------------------------------------------------------- /api/src/analyze/DataTypes.js: -------------------------------------------------------------------------------- 1 | const DataTypes = { 2 | String: "String", 3 | Integer: "Integer", 4 | Float: "Float", 5 | Boolean: "Boolean", 6 | Date: "Date", 7 | Time: "Time", 8 | LocalTime: "LocalTime", 9 | DateTime: "DateTime", 10 | LocalDateTime: "LocalDateTime", 11 | Duration: "Duration", 12 | Point: "Point", 13 | StringArray: "String Array", 14 | IntegerArray: "Integer Array", 15 | FloatArray: "Float Array", 16 | BooleanArray: "Boolean Array", 17 | DateArray: "Date Array", 18 | TimeArray: "Time Array", 19 | LocalTimeArray: "LocalTime Array", 20 | DateTimeArray: "DateTime Array", 21 | LocalDateTimeArray: "LocalDateTime Array", 22 | DurationArray: "Duration Array", 23 | PointArray: "Point Array" 24 | }; 25 | 26 | export default DataTypes; 27 | -------------------------------------------------------------------------------- /api/src/analyze/tableValueMap.test.js: -------------------------------------------------------------------------------- 1 | 2 | import { isInteger, isDateString } from "./tableValueMap"; 3 | 4 | test('isInteger', () => { 5 | expect(isInteger(1)).toBe(true); 6 | expect(isInteger(1.1)).toBe(false); 7 | expect(isInteger(1.0)).toBe(true); 8 | expect(isInteger("1")).toBe(false); 9 | expect(isInteger("foo")).toBe(false); 10 | }); 11 | 12 | test('isDateString', () => { 13 | expect(isDateString('02-02-2023')).toBe(true); 14 | expect(isDateString('02/02/2023')).toBe(true); 15 | expect(isDateString('02/32/2023')).toBe(false); 16 | expect(isDateString('20230202')).toBe(true); 17 | expect(isDateString('1P32')).toBe(false); 18 | }); -------------------------------------------------------------------------------- /api/src/cypher/ds_workbench/cypher_constraints.cypher: -------------------------------------------------------------------------------- 1 | CREATE CONSTRAINT ON (n:DataModel) ASSERT n.key IS UNIQUE; 2 | CREATE CONSTRAINT ON (n:DataModelMetadata) ASSERT n.key IS UNIQUE; 3 | CREATE CONSTRAINT ON (n:NodeLabel) ASSERT n.key IS UNIQUE; 4 | CREATE CONSTRAINT ON (n:RelationshipType) ASSERT n.key IS UNIQUE; 5 | CREATE CONSTRAINT ON (n:PropertyDefinition) ASSERT n.key IS UNIQUE; 6 | 7 | CREATE CONSTRAINT ON (n:Customer) ASSERT n.key IS UNIQUE; 8 | CREATE CONSTRAINT ON (n:Author) ASSERT n.key IS UNIQUE; 9 | CREATE CONSTRAINT ON (n:Tag) ASSERT n.tag IS UNIQUE; 10 | CREATE CONSTRAINT ON (n:Industry) ASSERT n.name IS UNIQUE; 11 | CREATE CONSTRAINT ON (n:UseCase) ASSERT n.name IS UNIQUE; 12 | 13 | CREATE CONSTRAINT ON (n:SecurityOrganization) ASSERT n.name IS UNIQUE; 14 | CREATE CONSTRAINT ON (n:EmailDomain) ASSERT n.name IS UNIQUE; 15 | CREATE CONSTRAINT ON (n:Tool) ASSERT n.name IS UNIQUE; 16 | CREATE CONSTRAINT ON (n:Feature) ASSERT n.name IS UNIQUE; 17 | CREATE CONSTRAINT ON (n:SoftwareEdition) ASSERT n.name IS UNIQUE; 18 | 19 | CREATE INDEX ON :Customer(name); 20 | CREATE INDEX ON :Author(name); 21 | 22 | CREATE CONSTRAINT ON (n:User) ASSERT n.email IS UNIQUE; 23 | CREATE CONSTRAINT ON (n:UserSettings) ASSERT n.email IS UNIQUE; 24 | 25 | CREATE CONSTRAINT ON (n:DBConnection) ASSERT n.id IS UNIQUE; 26 | 27 | CALL db.index.fulltext.createNodeIndex("dataModelMetadata", 28 | ["DataModelMetadata","Tag","UseCase","Industry","Author","Customer"],["title", "description","name","tag"]); 29 | 30 | CREATE CONSTRAINT ON (n:GraphDoc) ASSERT n.key IS UNIQUE; 31 | CREATE CONSTRAINT ON (n:GraphDocMetadata) ASSERT n.key IS UNIQUE; 32 | 33 | CALL db.index.fulltext.createNodeIndex("graphDocCypherSearch", 34 | ["GraphDocMetadata","Tag","Customer","CypherBuilder","CypherStatement"],["title", "description", "name", "tag", "cypherTitle", "cypherStatementSearchText"]); 35 | -------------------------------------------------------------------------------- /api/src/cypher/systemMessages.cypher: -------------------------------------------------------------------------------- 1 | // add system message to org 2 | WITH { 3 | numDaysValid: 7, // use 0 to not expire 4 | message: 'The system will be going down for maintenance on Friday, April 16 at 6PM Eastern time', 5 | orgName: 'Neo4j' 6 | } as params 7 | MATCH (org:SecurityOrganization {name: params.orgName}) 8 | MERGE (message:SystemMessage {key: apoc.create.uuid()}) 9 | SET message += { 10 | message: params.message, 11 | dateAdded: timestamp(), 12 | validUntil: timestamp() + params.numDaysValid*24*3600*1000 13 | } 14 | MERGE (org)-[:SYSTEM_MESSAGE]->(message) 15 | 16 | 17 | // add system message to all orgs 18 | WITH { 19 | numDaysValid: 7, // use 0 to not expire 20 | message: 'The system will be going down for maintenance on Friday, April 16 at 6PM Eastern time', 21 | orgName: 'Neo4j' 22 | } as params 23 | MERGE (message:SystemMessage {key: apoc.create.uuid()}) 24 | SET message += { 25 | message: params.message, 26 | dateAdded: timestamp(), 27 | validUntil: timestamp() + params.numDaysValid*24*3600*1000 28 | } 29 | WITH params, message 30 | MATCH (org:SecurityOrganization) 31 | MERGE (org)-[:SYSTEM_MESSAGE]->(message) -------------------------------------------------------------------------------- /api/src/data/neo4jtobigquery.js: -------------------------------------------------------------------------------- 1 | export const data = 2 | { 3 | "neoConnectionInfo": { 4 | "database": "neo4j" 5 | }, 6 | "queryInfo": { 7 | "cypher": "CALL gds.graph.streamNodeProperties('ClientsAndTransactions', ['amount']) YIELD nodeId, nodeProperty, propertyValue RETURN gds.util.asNode(nodeId).id AS name, nodeProperty, propertyValue ORDER BY name, nodeProperty", 8 | "outputFields": [ 9 | { 10 | "name": "name", 11 | "type": "String" 12 | }, 13 | { 14 | "name": "nodeProperty", 15 | "type": "String" 16 | }, 17 | { 18 | "name": "propertyValue", 19 | "type": "Float" 20 | } 21 | ] 22 | }, 23 | "bigQueryOutput": { 24 | "projectId": "big-query-project-id", 25 | "datasetId": "dataset-id", 26 | "tableName": "TableNameToTransfer" 27 | } 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /api/src/debug/debug.js: -------------------------------------------------------------------------------- 1 | 2 | import { doTransform } from "./DataModelMetadataTransform"; 3 | 4 | var dataModelMetadataDoc = ` 5 | { 6 | "dataModelMetadata": { 7 | "key": "222", 8 | "title": "Test GraphDoc", 9 | "description": "", 10 | "notes": "", 11 | "dateCreated": "1588193382680", 12 | "dateUpdated": "1590246440018", 13 | "isPublic": false, 14 | "upsertTags": [ 15 | { 16 | "key": "333", 17 | "tag": "Sports" 18 | } 19 | ], 20 | "removeTags": [ 21 | 22 | ], 23 | "upsertCustomers": [ 24 | { 25 | "key": "444", 26 | "name": "Neo4j" 27 | } 28 | ], 29 | "removeCustomers": [ 30 | 31 | ] 32 | } 33 | } 34 | ` 35 | 36 | var dataModelMetadataDocJSON = JSON.parse(dataModelMetadataDoc); 37 | //console.log(dataModelMetadataDocJSON); 38 | var result = doTransform(dataModelMetadataDocJSON); 39 | console.log(JSON.stringify(result,2)); 40 | -------------------------------------------------------------------------------- /api/src/layout/colors.js: -------------------------------------------------------------------------------- 1 | 2 | import { getNodeLabelArray } from "./layout"; 3 | 4 | // from ColorPicker.js 5 | const DefaultColors = ["#000000", "#ffffff", "#f44336", "#e91e63", "#9c27b0", 6 | "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4", 7 | "#009688", "#4caf50", "#8bc34a", "#cddc39", "#ffeb3b", 8 | "#ffc107", "#ff9800", "#ff5722", "#795548", "#607d8b"]; 9 | 10 | // 1-based 11 | const SelectionOrder = [10,12,15,4,5,20,7,11,17,19,8,13,16,3,6,1,14,2,9,18]; 12 | 13 | function makeColorPalette () { 14 | let colors = SelectionOrder.map(oneBasedIndex => DefaultColors[oneBasedIndex-1]) 15 | colors.reverse(); 16 | return colors; 17 | } 18 | 19 | // this mutates colorPalette and re-hydrates it when it's empty 20 | function getNextColor (colorPalette) { 21 | let nextColor = colorPalette.pop(); 22 | if (!nextColor) { 23 | let newPalette = makeColorPalette(); 24 | newPalette.forEach(color => colorPalette.push(color)); 25 | nextColor = colorPalette.pop(); 26 | } 27 | return nextColor; 28 | } 29 | 30 | // this mutates the node labels within the data model by setting the color 31 | export function autoColor (dataModel) { 32 | let nodeLabels = getNodeLabelArray(dataModel); 33 | let colorPalette = makeColorPalette(); 34 | nodeLabels.forEach((nodeLabel) => { 35 | let color = getNextColor(colorPalette); 36 | nodeLabel.setColor(color); 37 | }) 38 | } -------------------------------------------------------------------------------- /api/src/license/licenseKeyConstants.js: -------------------------------------------------------------------------------- 1 | 2 | export const LicenseFileKeys = { 3 | SymmetricKey: 'EYx/Vu6zegTqJ/1Y1j6YRyyhOtd1tjag91YWGa65LyQ=', 4 | SigningPublicKey: 'A5C2HX5klK2eZjNmz/p6+EdIkxwTwN83ewilcNgQv14=' 5 | } 6 | 7 | export const Joiner = '_$$_'; 8 | -------------------------------------------------------------------------------- /api/src/models/bigQuery/bigQuery.js: -------------------------------------------------------------------------------- 1 | // https://cloud.google.com/bigquery/docs/reference/libraries 2 | 3 | let BigQuery = null; 4 | //console.log("Loading bigquery.ms"); 5 | 6 | const loadBigQueryModule = () => { 7 | if (!BigQuery) { 8 | try { 9 | console.log("Loading BigQuery"); 10 | let BigQueryModule = require('@google-cloud/bigquery'); 11 | BigQuery = BigQueryModule.BigQuery; 12 | console.log("BigQuery Loaded"); 13 | } catch (e) { 14 | console.log("can't initialize BigQuery", e); 15 | } 16 | } 17 | } 18 | 19 | var _bigquery = null; 20 | const getBigQuery = () => { 21 | if (!_bigquery) { 22 | loadBigQueryModule(); 23 | console.log("Initializing BigQuery"); 24 | _bigquery = new BigQuery(); 25 | console.log("BigQuery Initialized"); 26 | } 27 | return _bigquery; 28 | } 29 | 30 | export const runBigQuery = async (query, location) => { 31 | // For all options, see https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query 32 | const options = { 33 | query: query, 34 | // Location must match that of the dataset(s) referenced in the query. 35 | location: location, 36 | }; 37 | 38 | let bigquery = getBigQuery(); 39 | 40 | // Run the query as a job 41 | const [job] = await bigquery.createQueryJob(options); 42 | //console.log(`Job ${job.id} started.`); 43 | 44 | // Wait for the query to finish 45 | const [rows] = await job.getQueryResults(); 46 | 47 | // Print the results 48 | //console.log('Rows:'); 49 | //rows.forEach(row => console.log(row)); 50 | return rows; 51 | } 52 | 53 | const runTest = () => { 54 | const projectId = 'big-query-project-id'; 55 | const query = `SELECT schema_name FROM \`${projectId}\`.INFORMATION_SCHEMA.SCHEMATA` 56 | 57 | const rows = runBigQuery(query, 'US'); 58 | console.log('Rows:'); 59 | //rows.forEach(row => console.log(row)); 60 | } 61 | 62 | //runTest(); 63 | 64 | 65 | -------------------------------------------------------------------------------- /api/src/models/bigQuery/bigQuery.mjs: -------------------------------------------------------------------------------- 1 | // https://cloud.google.com/bigquery/docs/reference/libraries 2 | 3 | let BigQuery = null; 4 | console.log("Loading bigquery.mjs"); 5 | 6 | const loadBigQueryModule = () => { 7 | if (!BigQuery) { 8 | try { 9 | console.log("Loading BigQuery"); 10 | let BigQueryModule = require('@google-cloud/bigquery'); 11 | BigQuery = BigQueryModule.BigQuery; 12 | console.log("BigQuery Loaded"); 13 | } catch (e) { 14 | console.log("can't initialize BigQuery", e); 15 | } 16 | } 17 | } 18 | 19 | var _bigquery = null; 20 | const getBigQuery = () => { 21 | if (!_bigquery) { 22 | loadBigQueryModule(); 23 | console.log("Initializing BigQuery"); 24 | _bigquery = new BigQuery(); 25 | console.log("BigQuery Initialized"); 26 | } 27 | return _bigquery; 28 | } 29 | 30 | export const runBigQuery = async (query, location) => { 31 | // For all options, see https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query 32 | const options = { 33 | query: query, 34 | // Location must match that of the dataset(s) referenced in the query. 35 | location: location, 36 | }; 37 | 38 | let bigquery = getBigQuery(); 39 | 40 | // Run the query as a job 41 | const [job] = await bigquery.createQueryJob(options); 42 | //console.log(`Job ${job.id} started.`); 43 | 44 | // Wait for the query to finish 45 | const [rows] = await job.getQueryResults(); 46 | 47 | // Print the results 48 | //console.log('Rows:'); 49 | //rows.forEach(row => console.log(row)); 50 | return rows; 51 | } 52 | 53 | const runTest = async () => { 54 | const projectId = 'big-query-project-id'; 55 | const query = `SELECT schema_name FROM \`${projectId}\`.INFORMATION_SCHEMA.SCHEMATA` 56 | 57 | const rows = await runBigQuery(query, 'US'); 58 | console.log('Rows:'); 59 | console.log(rows); 60 | //rows.map(row => console.log(row)); 61 | } 62 | 63 | //runTest(); 64 | 65 | 66 | -------------------------------------------------------------------------------- /api/src/models/phase.js: -------------------------------------------------------------------------------- 1 | import { encrypt, decrypt } from "../util/encryption"; 2 | import { runQuery, initializeDriver } from "../util/db"; 3 | 4 | const getPhaseFromPhaseNode = (node) => { 5 | const phase = { 6 | ...node.properties, 7 | phaseType: getPhaseType(node), 8 | }; 9 | if (!("description" in phase)) { 10 | phase.description = ""; 11 | } 12 | return phase; 13 | }; 14 | 15 | const getPhaseType = (node) => { 16 | return node.labels.filter((label) => label != "Phase")[0]; 17 | }; 18 | 19 | export const editPhase = async (id, properties, context) => { 20 | const uri = process.env.KEYMAKER_NEO4J_URI; 21 | const user = encrypt(process.env.KEYMAKER_NEO4J_USER); 22 | const password = encrypt(process.env.KEYMAKER_NEO4J_PASSWORD); 23 | const keymakerDBConnection = { 24 | id: "keymakerDBConnection", 25 | url: uri, 26 | encryptedUser: user, 27 | encryptedPassword: password, 28 | }; 29 | const query = ` 30 | MATCH (phase:Phase {id: $id})<-[:HAS_START_PHASE|NEXT_PHASE*]-(engine:Engine),(u:User{email: $email}) 31 | CALL apoc.util.validate(NOT (EXISTS((u)<-[:OWNER|MEMBER]-(engine)) or u:Admin),"permission denied",[0]) 32 | SET phase += $properties 33 | RETURN phase 34 | `; 35 | const driver = initializeDriver(keymakerDBConnection); 36 | const args = { id, properties, email: context.email }; 37 | const result = await runQuery(driver, query, args, process.env.KEYMAKER_NEO4J_DATABASE); 38 | return getPhaseFromPhaseNode(result.records[0].get("phase")); 39 | }; 40 | -------------------------------------------------------------------------------- /api/src/models/resultHelper.js: -------------------------------------------------------------------------------- 1 | 2 | export function processResult (result) { 3 | if (result && result.constructor && result.constructor.name === 'Neo4jError') { 4 | if (result.message && result.message.match) { 5 | if (result.message.match(/permission denied$/)) { 6 | throw new Error('You do not have sufficient permission for that operation'); 7 | } else if (result.message.match(/permission denied \(wrong org\)$/)) { 8 | throw new Error('Item does not exist'); 9 | } else { 10 | throw new Error(result.message); 11 | } 12 | } else { 13 | throw new Error(result.message); 14 | } 15 | } 16 | 17 | var resultSet = {headers: [], rows:[]} 18 | if (result && result.records && result.records.map) { 19 | result.records.map(record => { 20 | if (resultSet.headers.length === 0) { 21 | resultSet.headers = (record.keys) ? record.keys.filter(key => key !== 'null') : []; 22 | } 23 | var hasValues = false; 24 | var row = {}; 25 | record.keys.forEach(function (key) { 26 | var value = record.get(key); 27 | row[key] = value; 28 | if (value) 29 | hasValues = true; 30 | }); 31 | if (hasValues) 32 | resultSet.rows.push(row); 33 | }); 34 | } 35 | //console.log('resultSet: ', resultSet); 36 | return resultSet; 37 | } 38 | 39 | export function getFirstRowValue (resultSet, key, defaultValue) { 40 | if (resultSet && resultSet.rows && resultSet.rows.length === 1) { 41 | return resultSet.rows[0][key]; 42 | } else { 43 | if (defaultValue) { 44 | throw new Error(defaultValue); 45 | } else { 46 | return null; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /api/src/models/userRoles.js: -------------------------------------------------------------------------------- 1 | import { runQuery } from "../util/run"; 2 | import { processResult } from './resultHelper'; 3 | import { UpdateUserRoles, UpdateDatabaseUserRoles, GetUserRoles } from './userRolesCypher'; 4 | 5 | export const updateUserRoles = async (key, isPublic, userRoles, context) => { 6 | const args = {key: key, isPublic: isPublic, userRoles: userRoles, userEmail: context.email}; 7 | var cypher = UpdateUserRoles.replace('$$DocType', 'DataModel'); 8 | var result = await runQuery(cypher, args); 9 | result = processResult(result); 10 | // TODO: check for error 11 | //return getFirstRowValue(result, "success", "Error updating user roles"); 12 | return true; 13 | } 14 | 15 | export const updateUserRolesGraphDoc = async (key, isPublic, userRoles, context) => { 16 | const args = {key: key, isPublic: isPublic, userRoles: userRoles, userEmail: context.email}; 17 | var cypher = UpdateUserRoles.replace('$$DocType', 'GraphDoc'); 18 | var result = await runQuery(cypher, args); 19 | result = processResult(result); 20 | // TODO: check for error 21 | //return getFirstRowValue(result, "success", "Error updating user roles"); 22 | return true; 23 | } 24 | 25 | export const updateDatabaseUserRoles = async (databaseId, userRoles, context) => { 26 | const args = {databaseId: databaseId, userRoles: userRoles, userEmail: context.email}; 27 | var result = await runQuery(UpdateDatabaseUserRoles, args); 28 | result = processResult(result); 29 | // TODO: check for error 30 | //return getFirstRowValue(result, "success", "Error updating user roles"); 31 | return true; 32 | } 33 | 34 | export const getUserRolesForDataModel = async (key, context) => { 35 | const args = {key: key, userEmail: context.email}; 36 | var cypher = GetUserRoles.replace('$$DocType', 'DataModel'); 37 | var result = await runQuery(cypher, args); 38 | result = processResult(result); 39 | return result.rows; 40 | } 41 | 42 | export const getUserRolesForGraphDoc = async (key, context) => { 43 | const args = {key: key, userEmail: context.email}; 44 | var cypher = GetUserRoles.replace('$$DocType', 'GraphDoc'); 45 | var result = await runQuery(cypher, args); 46 | result = processResult(result); 47 | return result.rows; 48 | } 49 | -------------------------------------------------------------------------------- /api/src/resolvers/cypherAssociations.js: -------------------------------------------------------------------------------- 1 | import { 2 | associateScenarioToCypher, 3 | removeScenarioToCypherAssociation, 4 | listCypherStatements, 5 | searchCypherStatements 6 | } from "../models/cypherAssociations"; 7 | 8 | export default { 9 | Query: { 10 | listCypherStatementsX: (obj, args, context, resolveInfo) => { 11 | return listCypherStatements(args.myOrderBy, args.orderDirection, context); 12 | }, 13 | searchCypherStatementsX: (obj, args, context, resolveInfo) => { 14 | return searchCypherStatements(args.searchText, args.myOrderBy, args.orderDirection, context); 15 | } 16 | }, 17 | Mutation: { 18 | associateScenarioToCypher: async (obj, args, context, resolveInfo) => { 19 | const { 20 | scenarioGraphDocKey, scenarioKey, cypherGraphDocKey, cypherKey, isVisualCypher 21 | } = args; 22 | return await associateScenarioToCypher(scenarioGraphDocKey, scenarioKey, cypherGraphDocKey, cypherKey, isVisualCypher, context); 23 | }, 24 | removeScenarioToCypherAssociation: async (obj, args, context, resolveInfo) => { 25 | const { 26 | scenarioGraphDocKey, scenarioKey, cypherGraphDocKey, cypherKey 27 | } = args; 28 | return await removeScenarioToCypherAssociation(scenarioGraphDocKey, scenarioKey, cypherGraphDocKey, cypherKey, context); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /api/src/resolvers/dbConnection.js: -------------------------------------------------------------------------------- 1 | import { 2 | allDBConnectionsForUser, 3 | findDBConnection, 4 | createDBConnection, 5 | deleteDBConnection, 6 | getSchema, 7 | executeCypherQuery, 8 | getLabels, 9 | getPropertyNames, 10 | checkDBInfo, 11 | editDBConnection, 12 | getUsersForDB, 13 | canCurrentUserEdit, 14 | canCurrentUserDelete 15 | } from "../models/dbConnection"; 16 | 17 | export default { 18 | Query: { 19 | dbConnection: async (root, { id }, context) => { 20 | return await findDBConnection(id, context); 21 | }, 22 | getSchema: async (root, { input }, context) => { 23 | return await getSchema(input, context); 24 | }, 25 | executeCypherQuery: async (root, { input }, context) => { 26 | return await executeCypherQuery(input, context); 27 | }, 28 | allDBConnectionsForUser: async (root, args, context) => { 29 | return await allDBConnectionsForUser(context); 30 | } 31 | }, 32 | Mutation: { 33 | createDBConnection: async (root, { input }, context) => { 34 | return await createDBConnection(input, context); 35 | }, 36 | editDBConnection: async (root, { id, properties }, context) => { 37 | return await editDBConnection(id, properties, context); 38 | }, 39 | deleteDBConnection: async (root, { id }, context) => 40 | await deleteDBConnection(id, context) 41 | }, 42 | DBConnectionEx: { 43 | dbInfo: async (DBConnection, {}, context) => { 44 | return await checkDBInfo(DBConnection.id, context); 45 | }, 46 | users: async (DBConnection, {}, context) => { 47 | return await getUsersForDB(DBConnection.id, context); 48 | }, 49 | canCurrentUserEdit: async (DBConnection, {}, context) => { 50 | return await canCurrentUserEdit(DBConnection.id, context); 51 | }, 52 | canCurrentUserDelete: async (DBConnection, {}, context) => { 53 | return await canCurrentUserDelete(DBConnection.id, context); 54 | }, 55 | labels: async (DBConnection, {}, context) => { 56 | return []; 57 | }, 58 | propertyNames: async (DBConnection, { label }, context) => { 59 | return []; 60 | } 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /api/src/resolvers/json.js: -------------------------------------------------------------------------------- 1 | import GraphQLJSON from "graphql-type-json"; 2 | 3 | export default { 4 | JSON: GraphQLJSON 5 | }; 6 | -------------------------------------------------------------------------------- /api/src/resolvers/phase.js: -------------------------------------------------------------------------------- 1 | import { editPhase } from "../models/phase"; 2 | 3 | export default { 4 | Mutation: { 5 | editPhase: async (root, { id, input }, context, info) => { 6 | return await editPhase(id, input, context); 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /api/src/resolvers/userRoles.js: -------------------------------------------------------------------------------- 1 | import { 2 | getUserRolesForDataModel, 3 | getUserRolesForGraphDoc, 4 | updateUserRoles, 5 | updateUserRolesGraphDoc, 6 | updateDatabaseUserRoles 7 | } from "../models/userRoles"; 8 | 9 | export default { 10 | Query: { 11 | getUserRolesForDataModel: async (root, args, context, info) => { 12 | return await getUserRolesForDataModel(args.dataModelKey, context); 13 | }, 14 | getUserRolesForGraphDoc: async (root, args, context, info) => { 15 | return await getUserRolesForGraphDoc(args.key, context); 16 | } 17 | }, 18 | Mutation: { 19 | updateUserRoles: async (obj, args, context, resolveInfo) => { 20 | return await updateUserRoles(args.dataModelKey, args.isPublic, args.userRoles, context); 21 | }, 22 | updateUserRolesGraphDoc: async (obj, args, context, resolveInfo) => { 23 | return await updateUserRolesGraphDoc(args.key, args.isPublic, args.userRoles, context); 24 | }, 25 | updateDatabaseUserRoles: async (obj, args, context, resolveInfo) => { 26 | return await updateDatabaseUserRoles(args.databaseId, args.userRoles, context); 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /api/src/types/cypherAssociations.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | type CypherStatementResult @exclude { 4 | key: ID! 5 | subKey: ID 6 | cypherWorkbenchVersion: String 7 | title: String 8 | description: String 9 | customers: [Customer!]! @relationship(type:"HAS_CUSTOMER", direction:OUT) 10 | tags: [Tag!]! @relationship(type:"HAS_TAG", direction:OUT) 11 | owners: [User] 12 | dateCreated: String 13 | dateUpdated: String 14 | isPublic: Boolean 15 | userRole: String 16 | userIsCreator: Boolean 17 | isVisualCypher: Boolean 18 | cypher: String 19 | } 20 | 21 | type Query { 22 | listCypherStatementsX(myOrderBy: String, orderDirection: String): [CypherStatementResult] 23 | searchCypherStatementsX(searchText: String, myOrderBy: String, orderDirection: String): [CypherStatementResult] 24 | } 25 | 26 | type Mutation { 27 | associateScenarioToCypher(scenarioGraphDocKey: String, scenarioKey: String, cypherGraphDocKey: String, cypherKey: String, isVisualCypher: Boolean): Boolean 28 | removeScenarioToCypherAssociation(scenarioGraphDocKey: String, scenarioKey: String, cypherGraphDocKey: String, cypherKey: String): Boolean 29 | } 30 | `; -------------------------------------------------------------------------------- /api/src/types/dataTransfer.js: -------------------------------------------------------------------------------- 1 | 2 | export default ` 3 | 4 | input JobInfo { 5 | jobType: String 6 | jobParameters: JSON 7 | } 8 | 9 | type JobStatusVariables @exclude { 10 | STATUS: JSON 11 | MAX: String 12 | COUNTER: String 13 | } 14 | 15 | type JobDetails @exclude { 16 | service: String! 17 | id: String 18 | startDate: String 19 | endDate: String 20 | statusDescription: String 21 | statusVariables: JSON 22 | } 23 | 24 | type PipelineStatus @exclude { 25 | name: String 26 | id: String 27 | } 28 | 29 | type Query { 30 | getDataSourceSchema (connectionInfo: JSON): JSON 31 | jobStatus (jobId: String): JSON 32 | getProjects: JSON 33 | getDatasets (projectId: String): JSON 34 | getDatasetSchema (projectId: String, datasetId: String): JSON 35 | getPipelineDetails(serviceId: String): PipelineStatus 36 | getJobDetails (service: String, id: String): JSON 37 | getAllJobs: JSON 38 | } 39 | 40 | type Mutation { 41 | runJob (jobInfo: String): JSON 42 | runJobAsync (jobInfo: String): JSON 43 | runWorkflow (serviceId: String, mappingData: JSON): JSON 44 | runNeo4jToBigQuery (serviceId: String, cypherStatement: JSON): JSON 45 | runNeo4jToNeo4j(serviceId: String, neoMapping: JSON): JSON 46 | } 47 | `; 48 | 49 | -------------------------------------------------------------------------------- /api/src/types/json.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | scalar JSON 3 | `; 4 | -------------------------------------------------------------------------------- /api/src/types/phase.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | type Phase @exclude { 4 | id: ID! 5 | name: String! 6 | description: String! 7 | phaseType: String! 8 | active: Boolean! 9 | showCypher: Boolean 10 | cypherQuery: String! 11 | cypherWorkbenchCypherBuilderKey: ID 12 | } 13 | 14 | input EditPhaseInput { 15 | name: String 16 | description: String 17 | active: Boolean 18 | showCypher: Boolean 19 | cypherQuery: String 20 | inverted: Boolean 21 | maxAmount: Int 22 | cypherWorkbenchCypherBuilderKey: ID 23 | } 24 | 25 | type Mutation { 26 | editPhase(id: ID!, input: EditPhaseInput!): Phase 27 | } 28 | `; 29 | -------------------------------------------------------------------------------- /api/src/types/userRoles.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | type UserRole @exclude { 4 | email: String 5 | picture: String 6 | role: String 7 | isCreator: Boolean 8 | } 9 | 10 | input UserRoleInput { 11 | email: String 12 | role: String 13 | removeUser: Boolean 14 | } 15 | 16 | type Query { 17 | getUserRolesForDataModel(dataModelKey: String): [UserRole] 18 | getUserRolesForGraphDoc(key: String): [UserRole] 19 | } 20 | 21 | type Mutation { 22 | updateUserRoles(dataModelKey: String, isPublic: Boolean, userRoles:[UserRoleInput]): Boolean 23 | updateUserRolesGraphDoc(key: String, isPublic: Boolean, userRoles:[UserRoleInput]): Boolean 24 | updateDatabaseUserRoles(databaseId: String, userRoles:[UserRoleInput]): Boolean 25 | } 26 | 27 | `; 28 | -------------------------------------------------------------------------------- /api/src/ui/common/parse/antlr/AutocompleteErrorListener.js: -------------------------------------------------------------------------------- 1 | // from article https://medium.com/dailyjs/compiler-in-javascript-using-antlr-9ec53fd2780f 2 | // and https://blog.rapid7.com/2015/06/29/how-to-implement-antlr4-autocomplete/ 3 | //import antlr4 from 'antlr4/src/antlr4'; 4 | //import antlr4 from 'antlr4/src/antlr4/index'; 5 | import antlr4 from 'antlr4'; 6 | import { processTokens } from '../parseUtil'; 7 | 8 | /** 9 | * Autocomplete Error Listener 10 | * 11 | * @returns {object} 12 | */ 13 | export class AutocompleteErrorListener extends antlr4.error.ErrorListener { 14 | 15 | constructor (originalCypher) { 16 | super(); 17 | this.errorOccurred = false; 18 | this.tokens = []; 19 | this.originalCypher = originalCypher; 20 | if (this.originalCypher && this.originalCypher.split) { 21 | this.cypherLines = this.originalCypher.split('\n'); 22 | } 23 | } 24 | 25 | /** 26 | * Checks syntax error 27 | * 28 | * @param {object} recognizer The parsing support code essentially. Most of it is error recovery stuff 29 | * @param {object} symbol Offending symbol 30 | * @param {int} line Line of offending symbol 31 | * @param {int} column Position in line of offending symbol 32 | * @param {string} message Error message 33 | * @param {string} payload Stack trace 34 | */ 35 | syntaxError = (recognizer, symbol, line, column, message, payload) => { 36 | // https://blog.rapid7.com/2015/06/29/how-to-implement-antlr4-autocomplete/ 37 | 38 | this.errorOccurred = true; 39 | 40 | var parser = recognizer._ctx.parser; 41 | var tokens = parser.getTokenStream().tokens; 42 | 43 | this.tokens = processTokens(tokens, parser, this.cypherLines, { leaveSpaces: true }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /api/src/ui/common/parse/antlr/ErrorListener.js: -------------------------------------------------------------------------------- 1 | // from article https://medium.com/dailyjs/compiler-in-javascript-using-antlr-9ec53fd2780f 2 | //import antlr4 from 'antlr4/src/antlr4'; 3 | import antlr4 from 'antlr4'; 4 | 5 | export class SyntaxGenericError extends Error { 6 | constructor(syntaxObject) { 7 | super("Syntax error"); 8 | this.name = "SyntaxGenericError"; 9 | this.symbol = syntaxObject.symbol; 10 | //Object.keys(syntaxObject.symbol).map(key => console.log('symbol key ' + key + ' = ' + syntaxObject.symbol[key])); 11 | this.line = syntaxObject.line; 12 | this.column = syntaxObject.column; 13 | //this.message = `Unexpected symbol ${this.symbol}, line: ${this.line}, column: ${this.column}, message: ${syntaxObject.message}`; 14 | this.message = syntaxObject.message; 15 | } 16 | } 17 | 18 | /** 19 | * Custom Error Listener 20 | * 21 | * @returns {object} 22 | */ 23 | export class CustomErrorListener extends antlr4.error.ErrorListener { 24 | /** 25 | * Checks syntax error 26 | * 27 | * @param {object} recognizer The parsing support code essentially. Most of it is error recovery stuff 28 | * @param {object} symbol Offending symbol 29 | * @param {int} line Line of offending symbol 30 | * @param {int} column Position in line of offending symbol 31 | * @param {string} message Error message 32 | * @param {string} payload Stack trace 33 | */ 34 | syntaxError(recognizer, symbol, line, column, message, payload) { 35 | throw new SyntaxGenericError({symbol, line, column, message}); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /api/src/ui/common/parse/antlr/antlr-4.10.1-complete.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo4j-labs/cypher-workbench/1689899a882edc793d30603fbb31fcb0ce1e63c4/api/src/ui/common/parse/antlr/antlr-4.10.1-complete.jar -------------------------------------------------------------------------------- /api/src/ui/common/parse/antlr/parseReadme.txt: -------------------------------------------------------------------------------- 1 | If you make changes to the Cypher.g4 Antlr grammar, you should run this command from the folder that contains the Cypher.g4 file 2 | 3 | This is the new way (from Jon Harris enhanced cypher-editor project): 4 | ### cypher-antlr4 5 | 6 | This package contains: 7 | 8 | - JavaScript code generated from an antlr4 g4 grammar file. 9 | - The antlr4 g4 grammar file. 10 | - An antlr 4 java jar file used to generate the code. 11 | 12 | The `antlrGenerate` script in `package.json` gives an example of how to generate the source code yourself. 13 | 14 | ### General usage of Antlr 15 | https://github.com/antlr/antlr4/blob/master/doc/javascript-target.md 16 | 17 | ### Testing 18 | For testing, I was getting this error a lot: 19 | 20 | SyntaxError: Cannot use import statement outside a module 21 | 22 | until I added this to the package.json from the suggestion of this article: https://github.com/facebook/create-react-app/issues/9938 23 | 24 | "jest": { 25 | "transform": { 26 | "^.+\\.[t|j]s?$": "babel-jest" 27 | }, 28 | "transformIgnorePatterns": ["node_modules/(?!@antlr4)/"] 29 | } 30 | 31 | 32 | This is the old way, but as of Antlr 4.7.1 this way no longer works: 33 | (error(31): ANTLR cannot generate Javascript code as of version 4.7.1) 34 | 35 | antlr4 -Dlanguage=JavaScript Cypher.g4 36 | 37 | 38 | You may need to change these lines: 39 | require('antlr4/index'); 40 | 41 | to this: 42 | require('antlr4/src/antlr4/index'); 43 | -------------------------------------------------------------------------------- /api/src/ui/common/parse/antlr/withVariable.js: -------------------------------------------------------------------------------- 1 | 2 | export class WithVariable { 3 | 4 | constructor (properties) { 5 | properties = properties || {}; 6 | var { 7 | key, 8 | expression, 9 | variable, 10 | } = properties; 11 | 12 | this.key = key; 13 | this.expression = expression; 14 | this.variable = variable; 15 | } 16 | 17 | isAliased = () => (this.expression && this.variable) ? true : false; 18 | isAsterisk = () => (this.expression === '*') ? true : false; 19 | 20 | isEqualTo = (variable) => variable 21 | && this.key === variable.key 22 | && this.expression === variable.expression 23 | && this.variable === variable.variable 24 | } -------------------------------------------------------------------------------- /api/src/ui/dataModel/DataTypes.js: -------------------------------------------------------------------------------- 1 | const DataTypes = { 2 | String: "String", 3 | Integer: "Integer", 4 | Float: "Float", 5 | Boolean: "Boolean", 6 | Date: "Date", 7 | Time: "Time", 8 | LocalTime: "LocalTime", 9 | DateTime: "DateTime", 10 | LocalDateTime: "LocalDateTime", 11 | Duration: "Duration", 12 | Point: "Point", 13 | StringArray: "String Array", 14 | IntegerArray: "Integer Array", 15 | FloatArray: "Float Array", 16 | BooleanArray: "Boolean Array", 17 | DateArray: "Date Array", 18 | TimeArray: "Time Array", 19 | LocalTimeArray: "LocalTime Array", 20 | DateTimeArray: "DateTime Array", 21 | LocalDateTimeArray: "LocalDateTime Array", 22 | DurationArray: "Duration Array", 23 | PointArray: "Point Array" 24 | }; 25 | 26 | export default DataTypes; 27 | 28 | -------------------------------------------------------------------------------- /api/src/ui/dataModel/JaroWrinker.js: -------------------------------------------------------------------------------- 1 | 2 | // taken from https://medium.com/@sumn2u/string-similarity-comparision-in-js-with-examples-4bae35f13968 3 | export default function JaroWrinker (s1, s2) { 4 | var m = 0, i, j, n_trans; 5 | 6 | // Exit early if either are empty. 7 | if ( s1.length === 0 || s2.length === 0 ) { 8 | return 0; 9 | } 10 | 11 | // Exit early if they're an exact match. 12 | if ( s1 === s2 ) { 13 | return 1; 14 | } 15 | 16 | var range = (Math.floor(Math.max(s1.length, s2.length) / 2)) - 1, 17 | s1Matches = new Array(s1.length), 18 | s2Matches = new Array(s2.length); 19 | 20 | for ( i = 0; i < s1.length; i++ ) { 21 | var low = (i >= range) ? i - range : 0, 22 | high = (i + range <= s2.length) ? (i + range) : (s2.length - 1); 23 | 24 | for ( j = low; j <= high; j++ ) { 25 | if ( s1Matches[i] !== true && s2Matches[j] !== true && s1[i] === s2[j] ) { 26 | ++m; 27 | s1Matches[i] = s2Matches[j] = true; 28 | break; 29 | } 30 | } 31 | } 32 | 33 | // Exit early if no matches were found. 34 | if ( m === 0 ) { 35 | return 0; 36 | } 37 | 38 | // Count the transpositions. 39 | var k = n_trans = 0; 40 | 41 | for ( i = 0; i < s1.length; i++ ) { 42 | if ( s1Matches[i] === true ) { 43 | for ( j = k; j < s2.length; j++ ) { 44 | if ( s2Matches[j] === true ) { 45 | k = j + 1; 46 | break; 47 | } 48 | } 49 | 50 | if ( s1[i] !== s2[j] ) { 51 | ++n_trans; 52 | } 53 | } 54 | } 55 | 56 | var weight = (m / s1.length + m / s2.length + (m - (n_trans / 2)) / m) / 3, 57 | l = 0, 58 | p = 0.1; 59 | 60 | if ( weight > 0.7 ) { 61 | while ( s1[l] === s2[l] && l < 4 ) { 62 | ++l; 63 | } 64 | 65 | weight = weight + l * p * (1 - weight); 66 | } 67 | 68 | return weight; 69 | } 70 | -------------------------------------------------------------------------------- /api/src/ui/dataModel/helper.js: -------------------------------------------------------------------------------- 1 | 2 | export function smartQuote (text) { 3 | if (text && text.match && text.match(/ /)) { 4 | return '`' + text + '`'; 5 | } else { 6 | return text; 7 | } 8 | } 9 | 10 | export function lowerFirstLetter (text) { 11 | if (text && text.length > 0 && typeof(text) === 'string') { 12 | return text.substring(0,1).toLowerCase() + text.substring(1); 13 | } else { 14 | return text; 15 | } 16 | } -------------------------------------------------------------------------------- /api/src/ui/syncSrcFiles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cp ../../../ui/src/dataModel/dataModel.js dataModel 3 | cp ../../../ui/src/common/parse/parseCypher.js common/parse 4 | cp -R ../../../ui/src/common/parse/antlr common/parse 5 | cp ../../../ui/src/dataModel/DataTypes.js dataModel 6 | cp ../../../ui/src/dataModel/helper.js dataModel 7 | cp ../../../ui/src/dataModel/dataModelExtension.js dataModel 8 | cp ../../../ui/src/dataModel/JaroWrinker.js dataModel 9 | cp ../../../ui/src/components/canvas/d3/helpers.js components/canvas/d3 10 | cp ../../../ui/src/components/canvas/d3/grid.js components/canvas/d3 11 | echo "=== Important: make the following changes below ===" 12 | echo 13 | echo make these changes in CypherParser.js 14 | echo // commented line was failing until I updated to the line below 15 | echo // https://github.com/antlr/antlr4/issues/4139 16 | echo // const sharedContextCache = new antlr4.PredictionContextCache\(\); 17 | echo const sharedContextCache = new antlr4.atn.PredictionContextCache\(\); 18 | echo 19 | echo make these changes in CypherLexer.js 20 | echo // commented line was failing until I updated to the line below 21 | echo // https://github.com/antlr/antlr4/issues/4139 22 | echo // this._interp = new antlr4.atn.LexerATNSimulator\(this, atn, decisionsToDFA, new antlr4.PredictionContextCache\(\)\); 23 | echo " this._interp = new antlr4.atn.LexerATNSimulator(this, atn, decisionsToDFA, new antlr4.atn.PredictionContextCache());" 24 | 25 | 26 | -------------------------------------------------------------------------------- /api/src/users/createUser.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | createUserSignUp 4 | } from "../models/users"; 5 | import dotenv from "dotenv"; 6 | import { setDriver } from '../util/run'; 7 | import neo4j from "neo4j-driver"; 8 | dotenv.config(); 9 | 10 | function initNeoDriver () { 11 | 12 | var driverConfig = { 13 | disableLosslessIntegers: true 14 | }; 15 | if (!process.env.NEO4J_URI.match(/bolt\+s/) && !process.env.NEO4J_URI.match(/neo4j\+s/)) { 16 | driverConfig.encrypted = (process.env.NEO4J_ENCRYPTED === "true") ? true : false; 17 | } 18 | 19 | const uri = process.env.NEO4J_URI || "bolt://localhost:7687"; 20 | const user = process.env.NEO4J_USER || "neo4j"; 21 | const pass = process.env.NEO4J_PASSWORD || "password"; 22 | 23 | const driver = neo4j.driver( 24 | uri, 25 | neo4j.auth.basic(user, pass), 26 | driverConfig 27 | ); 28 | setDriver(driver); 29 | } 30 | initNeoDriver(); 31 | 32 | var args = process.argv; 33 | var commandLineArgs = args.slice(2); 34 | console.log(commandLineArgs); 35 | 36 | if (commandLineArgs.length < 4) { 37 | console.log("Usage: [adminEmail]"); 38 | process.exit(1); 39 | } 40 | 41 | var primaryOrganization = commandLineArgs[0]; 42 | var email = commandLineArgs[1]; 43 | var password = commandLineArgs[2]; 44 | var name = commandLineArgs[3]; 45 | var adminEmail = commandLineArgs[4]; 46 | var picture = ""; 47 | 48 | async function callCreateUser () { 49 | try { 50 | var result = await createUserSignUp({ 51 | primaryOrganization, 52 | email, 53 | password, 54 | name, 55 | picture 56 | }, 57 | { email: adminEmail }); 58 | console.log(result); 59 | } catch (e) { 60 | console.log(e); 61 | } 62 | process.exit(0); 63 | } 64 | callCreateUser(); 65 | -------------------------------------------------------------------------------- /api/src/users/decryptV1.js: -------------------------------------------------------------------------------- 1 | 2 | import { decryptV1 } from "../util/encryption"; 3 | 4 | var args = process.argv; 5 | var commandLineArgs = args.slice(2); 6 | console.log(commandLineArgs); 7 | 8 | if (commandLineArgs.length < 1) { 9 | console.log("Usage: "); 10 | process.exit(1); 11 | } 12 | 13 | console.log(decryptV1(commandLineArgs[0])); -------------------------------------------------------------------------------- /api/src/users/encryptV1.js: -------------------------------------------------------------------------------- 1 | 2 | import { encryptV1 } from "../util/encryption"; 3 | 4 | var args = process.argv; 5 | var commandLineArgs = args.slice(2); 6 | console.log(commandLineArgs); 7 | 8 | if (commandLineArgs.length < 1) { 9 | console.log("Usage: "); 10 | process.exit(1); 11 | } 12 | 13 | console.log(encryptV1(commandLineArgs[0])); -------------------------------------------------------------------------------- /api/src/util/run.js: -------------------------------------------------------------------------------- 1 | import neo4j from "neo4j-driver"; 2 | import dotenv from "dotenv"; 3 | import { VERSION } from '../version'; 4 | 5 | dotenv.config(); 6 | console.log('process.env.NEO4J_URI: ' + process.env.NEO4J_URI); 7 | 8 | 9 | var driverConfig = { 10 | userAgent: `neo4j-cypher-workbench-api/v${VERSION}` 11 | }; 12 | if (!process.env.NEO4J_URI.match(/bolt\+s/) && !process.env.NEO4J_URI.match(/neo4j\+s/)) { 13 | driverConfig.encrypted = (process.env.NEO4J_ENCRYPTED === "true") ? true : false; 14 | } 15 | 16 | var driver; 17 | /* 18 | export const driver = neo4j.driver( 19 | process.env.NEO4J_URI || "bolt://localhost:7687", 20 | neo4j.auth.basic( 21 | process.env.NEO4J_USER || "neo4j", 22 | process.env.NEO4J_PASSWORD || "password" 23 | ), 24 | driverConfig 25 | ); 26 | */ 27 | 28 | export const setDriver = (localDriver) => { 29 | driver = localDriver; 30 | } 31 | 32 | export const runQuery = async (query, args) => { 33 | const session = (process.env.NEO4J_DATABASE) ? 34 | driver.session({database: process.env.NEO4J_DATABASE}) : driver.session(); 35 | //const session = driver.session(); 36 | 37 | try { 38 | const result= await session.run(query, args); 39 | session.close(); 40 | return result; 41 | } catch (error) { 42 | //console.log(error); 43 | session.close(); 44 | return error; 45 | } 46 | }; 47 | 48 | export const runQueryWithDriverAndConfig = async(localDriver, sessionParams, query, args, config) => { 49 | const session = (sessionParams && sessionParams.databaseName) ? 50 | localDriver.session({database: sessionParams.databaseName}) : localDriver.session(); 51 | try { 52 | const result= await session.run(query, args, config); 53 | session.close(); 54 | return result; 55 | } catch (error) { 56 | session.close(); 57 | return error; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /api/src/version.js: -------------------------------------------------------------------------------- 1 | 2 | // https://semver.org/ 3 | export const VERSION = '1.5.1'; 4 | -------------------------------------------------------------------------------- /docker/api/Dockerfile.api: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | RUN mkdir -p /app 4 | WORKDIR /app 5 | 6 | COPY node_modules /app/node_modules 7 | COPY dist /app/dist 8 | 9 | WORKDIR /app/dist 10 | 11 | EXPOSE 4000 12 | 13 | CMD ["node", "index.js"] 14 | -------------------------------------------------------------------------------- /docker/api/build_labs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo ">>> Removing ./api folder" 3 | ./cleanup_api.sh $1 4 | echo ">>> Copying ./api folder without node_modules" 5 | rsync -av --progress ../../api . --exclude node_modules 6 | cd api 7 | rm ./license.lic 8 | rm ./.env.development* 9 | rm ./.env.my.development* 10 | rm ./.env.test* 11 | rm ./.env.production* 12 | echo ">>> Doing Cypher Workbench Labs build" 13 | #Commenting out so the enterprise files remain 14 | if [ "$1" = "-skipNpmInstall" ] 15 | then 16 | echo ">>> Skipping npm install" 17 | mv ../keep/node_modules . 18 | else 19 | echo ">>> Doing npm install" 20 | npm install 21 | fi 22 | echo ">>> Doing npm build" 23 | npm run build 24 | cd .. 25 | cp ./api.env ./api/dist/.env 26 | echo ">>> Doing docker build" 27 | docker build -f Dockerfile.api ./api -t cypher-workbench-api:${WORKBENCH_VERSION}_labs 28 | -------------------------------------------------------------------------------- /docker/api/cleanup_api.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ "$1" = "-skipNpmInstall" ] 3 | then 4 | mkdir ./keep 5 | mv ./api/node_modules ./keep 6 | find ./api -exec rm -rdf "{}" \; 7 | else 8 | find ./api -exec rm -rdf "{}" \; 9 | fi 10 | -------------------------------------------------------------------------------- /docker/build_labs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "$WORKBENCH_VERSION" ] 3 | then 4 | echo "WORKBENCH_VERSION must be set first by running command:" 5 | echo " source ./getVersion.sh" 6 | exit 1 7 | else 8 | echo "WORKBENCH_VERSION is ${WORKBENCH_VERSION}, ok to proceed? (y/n)" 9 | read isVersionOk 10 | if [ "$isVersionOk" != "y" ] 11 | then 12 | echo "Stopping build, re-run when version is ok." 13 | exit 1 14 | else 15 | echo "Building with WORKBENCH_VERSION ${WORKBENCH_VERSION}" 16 | fi 17 | fi 18 | 19 | echo "" 20 | echo "*** Building Labs ***" 21 | echo "" 22 | echo "*** Cleaning build folder ***" 23 | echo "" 24 | find ./build -exec rm -rdf "{}" \; 25 | mkdir build 26 | echo "" 27 | echo "*** Building api ***" 28 | echo "" 29 | cd api 30 | ./build_labs.sh $1 31 | cd .. 32 | echo "" 33 | echo "*** Building ui ***" 34 | echo "" 35 | cd ui 36 | ./build_labs.sh $1 37 | cd .. 38 | echo "" 39 | echo "*** Packaging docker images ***" 40 | echo "" 41 | docker save cypher-workbench-ui:${WORKBENCH_VERSION}_labs cypher-workbench-api:${WORKBENCH_VERSION}_labs | gzip > workbenchDocker.tar.gz 42 | mv workbenchDocker.tar.gz ./build 43 | ### Packaging Config 44 | echo "" 45 | echo "*** Packaging config ***" 46 | echo "" 47 | # copy license file 48 | rm ./cw-config/cw-config/cw-api/license.li* 49 | cp ../api/license.lic.labs ./cw-config/cw-config/cw-api/license.lic 50 | cd cw-config 51 | # copy create-user scripts 52 | mkdir scripts 53 | cd scripts 54 | cp ../../../api/scripts/create-user.sh . 55 | cp ../../../api/scripts/make-credential-file.sh . 56 | cd .. 57 | # compress all config sub-directories into tar file 58 | tar cvfz cw-config.tar.gz * 59 | mv cw-config.tar.gz ../build 60 | cd .. 61 | ### Creating docker-compose file 62 | sed 's|${WORKBENCH_VERSION}|'$WORKBENCH_VERSION'|g' ./workbench_labs_files/docker-compose-labs.yml > ./build/docker-compose.yml 63 | ### Copying workbench_labs utility files 64 | cp ./workbench_labs_files/workbench_labs ./build 65 | cp ./workbench_labs_files/envConfig.sh ./build 66 | cp ./workbench_labs_files/makeEnv.sh ./build 67 | cp ./workbench_labs_files/makeEnvConfig.sh ./build 68 | cp ./workbench_labs_files/makeNginxConf.sh ./build 69 | ### Packaging everything 70 | echo "" 71 | echo "*** Packaging everything ***" 72 | echo "" 73 | cd ./build 74 | tar cvf cypherWorkbenchLabs.tar * 75 | cd .. 76 | echo "*** Done ***" 77 | -------------------------------------------------------------------------------- /docker/cw-config/cw-config/cw-api/license.lic: -------------------------------------------------------------------------------- 1 | ==== Cypher Workbench 1.X - Labs ==== i8+LfNcu63uJdCyqc726o4dongCE3UdwtUiS5e7ZAxsUJASPjMjRKZHIzh8POZOKdDu6JvzsJzhwxQLwMjmjxDCE+3ds+dxxOLndBWsV1OdwI71sr7XameeONz6PGONTRWNeRw49pexdQWY0ecK+AA8/r/yA0amq6ZzqGfj6HlxXw6TiGE8KyE1DC+o/jj+7MnBIvO7eUF1xdGPtWEdCP5p7tm+5YQBerTrvT7deWzNLtrTYqgELlM7+KG5MSoAtfEiyyWzQp5v336L6vF2SIKMgbOC7Wbpp9DgCXy4mBBsf0bodzFU3vy6TrrhHDfbzRl1cXD+QuQuU05cXG/QAURxFT5ElgA3MX604a0mBCqTb4btCoCW6RpTeSAhJcEFZXfh/3Ld4GmN2ZoKcM7IALoLzrCy5N95VI+7TTXoCo+lkj8oRKpU6pLir2WVUJmnckYyGCvpmNuI/2WP6eTr8N9N6MN+mXw5Zj6nDR3WsKZTKtW1egCInc6g5E8qQW/5LqOCXr9lDP+PrMA6mN/mj90bpMnc=_$$_byYsLlj9+0QDc48jLVN77cKSAHiECAVplUdfYC+xeIeHmPbIDHSmeNKEPfajxYref33PrruSi6F8eMK3XmErBA== -------------------------------------------------------------------------------- /docker/cw-config/cw-config/cw-certificate/workbench.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDnjCCAoYCCQChPfUo2FnY3TANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMC 3 | VVMxCzAJBgNVBAgMAk1EMRIwEAYDVQQHDAlGcmVkZXJpY2sxDjAMBgNVBAoMBU5l 4 | bzRqMRIwEAYDVQQLDAlTb2x1dGlvbnMxGDAWBgNVBAMMD25lbzRqLnNvbHV0aW9u 5 | czEiMCAGCSqGSIb3DQEJARYTc29sdXRpb25zQG5lbzRqLmNvbTAeFw0yMzA4MDcy 6 | MTM0MTNaFw0zMzA4MDQyMTM0MTNaMIGQMQswCQYDVQQGEwJVUzELMAkGA1UECAwC 7 | TUQxEjAQBgNVBAcMCUZyZWRlcmljazEOMAwGA1UECgwFTmVvNGoxEjAQBgNVBAsM 8 | CVNvbHV0aW9uczEYMBYGA1UEAwwPbmVvNGouc29sdXRpb25zMSIwIAYJKoZIhvcN 9 | AQkBFhNzb2x1dGlvbnNAbmVvNGouY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 10 | MIIBCgKCAQEA1rQq46tDwSWPsomp+PhOE0XNlCNdEKHmokHcBHSfYiPUSSjXlUu0 11 | cv4quCYUJLHoPR9RuwqjbI/DWLkfx25gzm008KDb/Q5XWygBM7vX/vA5AnaATnvY 12 | mXR7c08pJrtAFl2a9R0ssKJdwS6Cj28MfEFlfSAZ7ATA1HFmtF1Z7TOfDRq4BGIV 13 | I8BStGNFBGpa/xfkj5S066dK97NQZJyc9sc1L7fBLCdjcvehwfxzUh8rZXwoZAu+ 14 | Q6qj7e9vEuEbtSDD4kqSdUvLeTbMBKBsk5fydodBHmeInFbfi3M2F5MXoVtThpoA 15 | kk8ZjcMFiREBTIE9ZDzLLGbheN/QfW2qzQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB 16 | AQCgqyjW8s3WKLdDPFPtmt+KBC9ztAWEMArc3b7O6O3z1OV4SxXx4/c6t1+vApUl 17 | jns/DESeIy42zk1+84Z6lssm1gYCOmurrP+aDNaqx77GsXk52yI6DuutOBxBcAuv 18 | Trb95ehDcGt928w5UKyCAmBN9Bf1kMBWPMw52l9BFUIGWb8txSBNUV2GMIU0FvFp 19 | yRY6fhxAwMJL2YJj1QN61I/GlGeHrTsthWvVfNznQqzIy8KiQIQc+x2ls9ewVPck 20 | BY3HzHWesFU8Nlg1Nj2dzQiJL72hQ64SrGA5KcJoI6PkYh3zzfTlqrWZD9+P2hdu 21 | Bo4a4UNAGYfmdbO9J5W+8FWy 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /docker/cw-config/cw-config/cw-certificate/workbench.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDWtCrjq0PBJY+y 3 | ian4+E4TRc2UI10QoeaiQdwEdJ9iI9RJKNeVS7Ry/iq4JhQkseg9H1G7CqNsj8NY 4 | uR/HbmDObTTwoNv9DldbKAEzu9f+8DkCdoBOe9iZdHtzTykmu0AWXZr1HSywol3B 5 | LoKPbwx8QWV9IBnsBMDUcWa0XVntM58NGrgEYhUjwFK0Y0UEalr/F+SPlLTrp0r3 6 | s1BknJz2xzUvt8EsJ2Ny96HB/HNSHytlfChkC75DqqPt728S4Ru1IMPiSpJ1S8t5 7 | NswEoGyTl/J2h0EeZ4icVt+LczYXkxehW1OGmgCSTxmNwwWJEQFMgT1kPMssZuF4 8 | 39B9barNAgMBAAECggEAUw+8j/uP6McpQn7znPgi7F3S3H6Z4HdiDF/CSI79ojAO 9 | TodYb8+r7djamgdRP6j8GblmGyBQfQ1ZG5fy7WqMVQ/2rSjuMoY70W42mNcRBvXU 10 | eFlz5ekPsL5ZjBCR8QP8FSr9adpxkZwe/T+LDwZ9JA0AwFmM7bwhBY8hlqGOyq66 11 | Za0iJ1aMbKPJX8+9g0NPMe/LDGxgGY1kyiuFobfRNg1rAq38EVjEucYdShpB0V+e 12 | TluaN07Wk2kJg20qgXPXhLdmdNOlIZIL8dNbD0hQ/HwIper38Uu5257T7IIiDF3u 13 | OKXoDW8tiUqwEdZaHvEIfWPvzoxF+ZQXqICSI5+8wQKBgQDvvLhIcow98pR3ueWh 14 | HLhCSSQcBURbOmju0X82BDHY2elr+/Zl+GRjcDsG8Od8x2F31ToSX1DbyQXXYSDo 15 | RlUEUCNHGbWCnjiqfWd1DCQYC3ubrefIxGUpO9WNLPGsAIgtvKIrLvEOgukqDoSH 16 | ujBLm+nvS4cOwhBnC19MwL/GWQKBgQDlRLeTXnRdxf/VC6T8Lu6X4VwJCbQqDila 17 | zPZ+NBi/prRgnb5rqsbqvaoi+6fQpDYfLYfFF3rKOTgLcydHtOBUx13H8YjnzaHY 18 | WM+DNZljALMD/RIc+IkyVKTy26XiEgjjZ8LdJXxJZQn1RGohlsmIXr2ACBG28bz9 19 | rt4rCvPhlQKBgDnZKgahTWHtVRIG03gq+/NSvtncE4CH+aYW+0FHhdezzXV3GR/F 20 | 7kNoY3XrT5B2c/h8hUTFpzdBrJ0qHMyvm/gsdjbD516bW0UYeYxu347Fxo/sSM/T 21 | RC3M+FzWiYJdpn6S0/bjnttHj4fMdQJjVSAJgUtyyCYxgc+7mMVmhWARAoGBANCe 22 | MLTmM9jIPDyttdjLE8wcAlUvAUNrU3IOIxU/bm2l0WhA4W6zHQGox9HBUDhn09+W 23 | 3H4ZGWA9pKO2ir2S9rXuG4W+YKcc0/I7DcgE06fkkQBGHV9DQAQORXG/MDh/1Jqo 24 | ZgY4/9kBGYiWUkRyIrv2CVUhAo2HdkMYBY0BEF3pAoGBAJnNhpuugvMVD+U/vFoY 25 | SvfotbEbUODnPJDJmVYjbONpafelBUGLiNW80IiXN8H9yBwRb+TEnbxtXL1bUh/E 26 | x8iES223oumnO0v5VwAZ86P80A30roP3ox2LvYTfwkPiN5IsIdgst8cZPnLGBkSk 27 | /G3Gn/Aee532Jfb+kPumjAZK 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/cw-config/cw-config/cw-db-setup/cypher_constraints_v4.3.cypher: -------------------------------------------------------------------------------- 1 | CREATE CONSTRAINT ON (n:DataModel) ASSERT n.key IS UNIQUE; 2 | CREATE CONSTRAINT ON (n:DataModelMetadata) ASSERT n.key IS UNIQUE; 3 | CREATE CONSTRAINT ON (n:NodeLabel) ASSERT n.key IS UNIQUE; 4 | CREATE CONSTRAINT ON (n:RelationshipType) ASSERT n.key IS UNIQUE; 5 | CREATE CONSTRAINT ON (n:PropertyDefinition) ASSERT n.key IS UNIQUE; 6 | 7 | CREATE CONSTRAINT ON (n:Customer) ASSERT n.key IS UNIQUE; 8 | CREATE CONSTRAINT ON (n:Author) ASSERT n.key IS UNIQUE; 9 | CREATE CONSTRAINT ON (n:Tag) ASSERT n.tag IS UNIQUE; 10 | CREATE CONSTRAINT ON (n:Industry) ASSERT n.name IS UNIQUE; 11 | CREATE CONSTRAINT ON (n:UseCase) ASSERT n.name IS UNIQUE; 12 | 13 | CREATE CONSTRAINT ON (n:SecurityOrganization) ASSERT n.name IS UNIQUE; 14 | CREATE CONSTRAINT ON (n:EmailDomain) ASSERT n.name IS UNIQUE; 15 | CREATE CONSTRAINT ON (n:Tool) ASSERT n.name IS UNIQUE; 16 | CREATE CONSTRAINT ON (n:Feature) ASSERT n.name IS UNIQUE; 17 | CREATE CONSTRAINT ON (n:SoftwareEdition) ASSERT n.name IS UNIQUE; 18 | 19 | CREATE INDEX ON :Customer(name); 20 | CREATE INDEX ON :Author(name); 21 | 22 | CREATE CONSTRAINT ON (n:User) ASSERT n.email IS UNIQUE; 23 | CREATE CONSTRAINT ON (n:UserSettings) ASSERT n.email IS UNIQUE; 24 | 25 | CREATE CONSTRAINT ON (n:DBConnection) ASSERT n.id IS UNIQUE; 26 | 27 | // CALL db.index.fulltext.createNodeIndex("dataModelMetadata", 28 | // ["DataModelMetadata","Tag","UseCase","Industry","Author","Customer"],["title", "description","name","tag"]); 29 | CREATE FULLTEXT INDEX dataModelMetadata FOR (n:DataModelMetadata|Tag|UseCase|Industry|Author|Customer) 30 | ON EACH [n.title, n.description, n.name, n.tag]; 31 | 32 | 33 | CREATE CONSTRAINT ON (n:GraphDoc) ASSERT n.key IS UNIQUE; 34 | CREATE CONSTRAINT ON (n:GraphDocMetadata) ASSERT n.key IS UNIQUE; 35 | 36 | // CALL db.index.fulltext.createNodeIndex("graphDocCypherSearch", 37 | // ["GraphDocMetadata","Tag","Customer","CypherBuilder","CypherStatement"],["title", "description", "name", "tag", "cypherTitle", "cypherStatementSearchText"]); 38 | CREATE FULLTEXT INDEX graphDocCypherSearch FOR (n:GraphDocMetadata|Tag|Customer|CypherBuilder|CypherStatement) 39 | ON EACH [n.title, n.description, n.name, n.tag, n.cypherTitle, n.cypherStatementSearchText]; 40 | -------------------------------------------------------------------------------- /docker/cw-config/cw-config/cw-db-setup/cypher_constraints_v4.4_to_5.cypher: -------------------------------------------------------------------------------- 1 | CREATE CONSTRAINT FOR (n:DataModel) REQUIRE n.key IS UNIQUE; 2 | CREATE CONSTRAINT FOR (n:DataModelMetadata) REQUIRE n.key IS UNIQUE; 3 | CREATE CONSTRAINT FOR (n:NodeLabel) REQUIRE n.key IS UNIQUE; 4 | CREATE CONSTRAINT FOR (n:RelationshipType) REQUIRE n.key IS UNIQUE; 5 | CREATE CONSTRAINT FOR (n:PropertyDefinition) REQUIRE n.key IS UNIQUE; 6 | 7 | CREATE CONSTRAINT FOR (n:Customer) REQUIRE n.key IS UNIQUE; 8 | CREATE CONSTRAINT FOR (n:Author) REQUIRE n.key IS UNIQUE; 9 | CREATE CONSTRAINT FOR (n:Tag) REQUIRE n.tag IS UNIQUE; 10 | CREATE CONSTRAINT FOR (n:Industry) REQUIRE n.name IS UNIQUE; 11 | CREATE CONSTRAINT FOR (n:UseCase) REQUIRE n.name IS UNIQUE; 12 | 13 | CREATE CONSTRAINT FOR (n:SecurityOrganization) REQUIRE n.name IS UNIQUE; 14 | CREATE CONSTRAINT FOR (n:EmailDomain) REQUIRE n.name IS UNIQUE; 15 | CREATE CONSTRAINT FOR (n:Tool) REQUIRE n.name IS UNIQUE; 16 | CREATE CONSTRAINT FOR (n:Feature) REQUIRE n.name IS UNIQUE; 17 | CREATE CONSTRAINT FOR (n:SoftwareEdition) REQUIRE n.name IS UNIQUE; 18 | 19 | CREATE INDEX FOR (c:Customer) ON (c.name); 20 | CREATE INDEX FOR (a:Author) ON (a.name); 21 | 22 | CREATE CONSTRAINT FOR (n:User) REQUIRE n.email IS UNIQUE; 23 | CREATE CONSTRAINT FOR (n:UserSettings) REQUIRE n.email IS UNIQUE; 24 | 25 | CREATE CONSTRAINT FOR (n:DBConnection) REQUIRE n.id IS UNIQUE; 26 | 27 | CREATE FULLTEXT INDEX dataModelMetadata FOR (n:DataModelMetadata|Tag|UseCase|Industry|Author|Customer) 28 | ON EACH [n.title, n.description, n.name, n.tag]; 29 | 30 | CREATE CONSTRAINT FOR (n:GraphDoc) REQUIRE n.key IS UNIQUE; 31 | CREATE CONSTRAINT FOR (n:GraphDocMetadata) REQUIRE n.key IS UNIQUE; 32 | 33 | CREATE FULLTEXT INDEX graphDocCypherSearch FOR (n:GraphDocMetadata|Tag|Customer|CypherBuilder|CypherStatement) 34 | ON EACH [n.title, n.description, n.name, n.tag, n.cypherTitle, n.cypherStatementSearchText]; 35 | -------------------------------------------------------------------------------- /docker/cw-config/cw-config/cw-ui-nginx/dhparam.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIICCAKCAgEA7UDMb0qXU4N895OjXkwQiHyPxYD0sgfXhBDMY6r/sarTuMfjksQ0 3 | bI/CJW2AGGXjrLsvKGQSextsr89nwpxDiqlgc8BrtxWoU1c/OkY/IFdD9vUnOERF 4 | by/Ry12roDw5lDbuVxCm0NR9kKwUnlt8ZTgYmurMXKXy+hmUBerZo/SYb/3eWkib 5 | HwySfwE0ITBJus3yRhL5e5boxv1TkcFwEamBRpg2DtNG1BQyBF41B0o4dPtUiFKE 6 | tUNYDfhkjDQRlL/C3aD8DacHvSWjEEy8dmVRDhGSjCcvGEnthGesF3LA3wYkD08n 7 | blFEZhEWoAT3oy5CZUw2xp529sIB/GtXsplW7RUTFacB2xNUrkTv8hzEBgc5Id0/ 8 | 1Zwyfm8TiprAs8GlWtk995jM7KIMPxYX8gEBmhBTQ9TTJ6FrhmxyeICELsVNEPK0 9 | E+aZEmt+0T1106qxy0a3AZ0lp1sQ0pR6gPjYrtCn9gews/AwZ4xg4amIE/OHIsY8 10 | vb3UtejSO0vCCaIJnJUxnTKXiZ56HuV/M9pn1YGNHqKcRnV/DrWS+Up5uK7jAWQX 11 | I0HFNpwor6FyfKBX0VgCGMJ8p9bnOQabqC9a/0leWyEuTyz9MjchsMJ6V4sPKgF+ 12 | Htua4IWrlM57tkGmeHMzS8FxqNWMyXH5Pp2NX8ar7bxx/ZQFi5N8UKMCAQI= 13 | -----END DH PARAMETERS----- 14 | -------------------------------------------------------------------------------- /docker/cw-config/cw-config/cw-ui-nginx/workbench-ssl-params.conf: -------------------------------------------------------------------------------- 1 | ssl_protocols TLSv1.3; 2 | ssl_prefer_server_ciphers on; 3 | #ssl_dhparam /etc/nginx/dhparam.pem; 4 | ssl_dhparam /usr/share/nginx/conf/dhparam.pem; 5 | ssl_ciphers EECDH+AESGCM:EDH+AESGCM; 6 | ssl_ecdh_curve secp384r1; 7 | ssl_session_timeout 10m; 8 | ssl_session_cache shared:SSL:10m; 9 | ssl_session_tickets off; 10 | ssl_stapling on; 11 | ssl_stapling_verify on; 12 | resolver 8.8.8.8 8.8.4.4 valid=300s; 13 | resolver_timeout 5s; 14 | # Disable strict transport security for now. You can uncomment the following 15 | # line if you understand the implications. 16 | #add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; 17 | add_header X-Frame-Options DENY; 18 | add_header X-Content-Type-Options nosniff; 19 | add_header X-XSS-Protection "1; mode=block"; 20 | -------------------------------------------------------------------------------- /docker/cw-config/cw-config/cw-ui-nginx/workbench.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDnjCCAoYCCQChPfUo2FnY3TANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMC 3 | VVMxCzAJBgNVBAgMAk1EMRIwEAYDVQQHDAlGcmVkZXJpY2sxDjAMBgNVBAoMBU5l 4 | bzRqMRIwEAYDVQQLDAlTb2x1dGlvbnMxGDAWBgNVBAMMD25lbzRqLnNvbHV0aW9u 5 | czEiMCAGCSqGSIb3DQEJARYTc29sdXRpb25zQG5lbzRqLmNvbTAeFw0yMzA4MDcy 6 | MTM0MTNaFw0zMzA4MDQyMTM0MTNaMIGQMQswCQYDVQQGEwJVUzELMAkGA1UECAwC 7 | TUQxEjAQBgNVBAcMCUZyZWRlcmljazEOMAwGA1UECgwFTmVvNGoxEjAQBgNVBAsM 8 | CVNvbHV0aW9uczEYMBYGA1UEAwwPbmVvNGouc29sdXRpb25zMSIwIAYJKoZIhvcN 9 | AQkBFhNzb2x1dGlvbnNAbmVvNGouY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 10 | MIIBCgKCAQEA1rQq46tDwSWPsomp+PhOE0XNlCNdEKHmokHcBHSfYiPUSSjXlUu0 11 | cv4quCYUJLHoPR9RuwqjbI/DWLkfx25gzm008KDb/Q5XWygBM7vX/vA5AnaATnvY 12 | mXR7c08pJrtAFl2a9R0ssKJdwS6Cj28MfEFlfSAZ7ATA1HFmtF1Z7TOfDRq4BGIV 13 | I8BStGNFBGpa/xfkj5S066dK97NQZJyc9sc1L7fBLCdjcvehwfxzUh8rZXwoZAu+ 14 | Q6qj7e9vEuEbtSDD4kqSdUvLeTbMBKBsk5fydodBHmeInFbfi3M2F5MXoVtThpoA 15 | kk8ZjcMFiREBTIE9ZDzLLGbheN/QfW2qzQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB 16 | AQCgqyjW8s3WKLdDPFPtmt+KBC9ztAWEMArc3b7O6O3z1OV4SxXx4/c6t1+vApUl 17 | jns/DESeIy42zk1+84Z6lssm1gYCOmurrP+aDNaqx77GsXk52yI6DuutOBxBcAuv 18 | Trb95ehDcGt928w5UKyCAmBN9Bf1kMBWPMw52l9BFUIGWb8txSBNUV2GMIU0FvFp 19 | yRY6fhxAwMJL2YJj1QN61I/GlGeHrTsthWvVfNznQqzIy8KiQIQc+x2ls9ewVPck 20 | BY3HzHWesFU8Nlg1Nj2dzQiJL72hQ64SrGA5KcJoI6PkYh3zzfTlqrWZD9+P2hdu 21 | Bo4a4UNAGYfmdbO9J5W+8FWy 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /docker/cw-config/cw-config/cw-ui-nginx/workbench.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDWtCrjq0PBJY+y 3 | ian4+E4TRc2UI10QoeaiQdwEdJ9iI9RJKNeVS7Ry/iq4JhQkseg9H1G7CqNsj8NY 4 | uR/HbmDObTTwoNv9DldbKAEzu9f+8DkCdoBOe9iZdHtzTykmu0AWXZr1HSywol3B 5 | LoKPbwx8QWV9IBnsBMDUcWa0XVntM58NGrgEYhUjwFK0Y0UEalr/F+SPlLTrp0r3 6 | s1BknJz2xzUvt8EsJ2Ny96HB/HNSHytlfChkC75DqqPt728S4Ru1IMPiSpJ1S8t5 7 | NswEoGyTl/J2h0EeZ4icVt+LczYXkxehW1OGmgCSTxmNwwWJEQFMgT1kPMssZuF4 8 | 39B9barNAgMBAAECggEAUw+8j/uP6McpQn7znPgi7F3S3H6Z4HdiDF/CSI79ojAO 9 | TodYb8+r7djamgdRP6j8GblmGyBQfQ1ZG5fy7WqMVQ/2rSjuMoY70W42mNcRBvXU 10 | eFlz5ekPsL5ZjBCR8QP8FSr9adpxkZwe/T+LDwZ9JA0AwFmM7bwhBY8hlqGOyq66 11 | Za0iJ1aMbKPJX8+9g0NPMe/LDGxgGY1kyiuFobfRNg1rAq38EVjEucYdShpB0V+e 12 | TluaN07Wk2kJg20qgXPXhLdmdNOlIZIL8dNbD0hQ/HwIper38Uu5257T7IIiDF3u 13 | OKXoDW8tiUqwEdZaHvEIfWPvzoxF+ZQXqICSI5+8wQKBgQDvvLhIcow98pR3ueWh 14 | HLhCSSQcBURbOmju0X82BDHY2elr+/Zl+GRjcDsG8Od8x2F31ToSX1DbyQXXYSDo 15 | RlUEUCNHGbWCnjiqfWd1DCQYC3ubrefIxGUpO9WNLPGsAIgtvKIrLvEOgukqDoSH 16 | ujBLm+nvS4cOwhBnC19MwL/GWQKBgQDlRLeTXnRdxf/VC6T8Lu6X4VwJCbQqDila 17 | zPZ+NBi/prRgnb5rqsbqvaoi+6fQpDYfLYfFF3rKOTgLcydHtOBUx13H8YjnzaHY 18 | WM+DNZljALMD/RIc+IkyVKTy26XiEgjjZ8LdJXxJZQn1RGohlsmIXr2ACBG28bz9 19 | rt4rCvPhlQKBgDnZKgahTWHtVRIG03gq+/NSvtncE4CH+aYW+0FHhdezzXV3GR/F 20 | 7kNoY3XrT5B2c/h8hUTFpzdBrJ0qHMyvm/gsdjbD516bW0UYeYxu347Fxo/sSM/T 21 | RC3M+FzWiYJdpn6S0/bjnttHj4fMdQJjVSAJgUtyyCYxgc+7mMVmhWARAoGBANCe 22 | MLTmM9jIPDyttdjLE8wcAlUvAUNrU3IOIxU/bm2l0WhA4W6zHQGox9HBUDhn09+W 23 | 3H4ZGWA9pKO2ir2S9rXuG4W+YKcc0/I7DcgE06fkkQBGHV9DQAQORXG/MDh/1Jqo 24 | ZgY4/9kBGYiWUkRyIrv2CVUhAo2HdkMYBY0BEF3pAoGBAJnNhpuugvMVD+U/vFoY 25 | SvfotbEbUODnPJDJmVYjbONpafelBUGLiNW80IiXN8H9yBwRb+TEnbxtXL1bUh/E 26 | x8iES223oumnO0v5VwAZ86P80A30roP3ox2LvYTfwkPiN5IsIdgst8cZPnLGBkSk 27 | /G3Gn/Aee532Jfb+kPumjAZK 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/cw-config/cw-config/cw-ui-template/env-config.js.template: -------------------------------------------------------------------------------- 1 | 2 | window._dynamicEnv_ = { 3 | // should match ${WORKBENCH_PROTOCOL}://${WORKBENCH_HOST}:${WORKBENCH_PORT}/graphql 4 | REACT_APP_GRAPHQL_URI: "${WORKBENCH_PROTOCOL}://${WORKBENCH_HOST}:${WORKBENCH_PORT}/graphql", 5 | 6 | // should match ${WORKBENCH_PROTOCOL}://${WORKBENCH_HOST}:${WORKBENCH_PORT} 7 | REACT_APP_BASE_URL: "${WORKBENCH_PROTOCOL}://${WORKBENCH_HOST}:${WORKBENCH_PORT}", 8 | 9 | // set this to the same value as docker-compose.yml AUTH_METHOD 10 | REACT_APP_AUTH_METHOD: "local", 11 | 12 | REACT_APP_EULA: "none", 13 | // set this to the same value as docker-compose.yml ENCRYPTION_KEY 14 | REACT_APP_ENCRYPTION_KEY: "workbenchEncryptionKey", 15 | 16 | REACT_APP_HELP_URL: "https://help.neo4j.solutions/neo4j-solutions/cypher-workbench/", 17 | 18 | REACT_APP_SHOW_CUSTOMERS_IN_SAVE_FORM: false 19 | } 20 | 21 | -------------------------------------------------------------------------------- /docker/cw-config/scripts/create-user.sh: -------------------------------------------------------------------------------- 1 | PRIMARY_ORGANIZATION=$2 2 | USERID=$3 3 | PASSWORD=$4 4 | NAME=$5 5 | PICTURE=$6 6 | echo "Creating $USERID" 7 | if [ "$#" -lt 5 ]; 8 | then 9 | echo 'Usage: ./create-user.sh [picture] [host] [graphql port] [https]' 10 | exit 1 11 | fi 12 | 13 | HOST=localhost 14 | if [[ -n "$7" ]]; then HOST=$7; fi 15 | 16 | #PORT=37400 17 | PORT=80 18 | if [[ -n "$8" ]]; then PORT=$8; fi 19 | 20 | PROTOCOL=http 21 | if [[ -n "$9" ]]; then PROTOCOL=$9; fi 22 | 23 | TYPE='"type": "Basic"' 24 | CREDENTIALS='"credentials": "'$(<$1)'"' 25 | CREDENTIALS_JSON="{ $TYPE, $CREDENTIALS}" 26 | #echo $CREDENTIALS_JSON 27 | 28 | curl "$PROTOCOL://$HOST:$PORT/graphql" \ 29 | -H 'content-type: application/json' \ 30 | -H "authorization: $CREDENTIALS_JSON" \ 31 | --data '{"query":"mutation {\n createUserSignUp(input: {primaryOrganization: \"'"$PRIMARY_ORGANIZATION"'\", email: \"'"$USERID"'\", password: \"'"$PASSWORD"'\", name: \"'"$NAME"'\", picture: \"'"$PICTURE"'\"}) {\n email\n name\n picture\n }\n}\n"}' \ 32 | --compressed 33 | -------------------------------------------------------------------------------- /docker/cw-config/scripts/make-credential-file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ "$#" -lt 3 ]] ; then 3 | echo "Usage: ./make_credential_file " 4 | exit 2 5 | fi 6 | CREDENTIALS=$1:$2 7 | ENCODED_CREDENTIALS=$(echo -n $CREDENTIALS | base64) 8 | echo $ENCODED_CREDENTIALS > $3 9 | cat $3 10 | -------------------------------------------------------------------------------- /docker/getVersion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | input="../ui/src/version.js" 3 | while IFS= read -r line 4 | do 5 | #echo "$line" 6 | result=`echo "$line" | sed "s/export const VERSION = '\([^ ]*\)';/\1/"` 7 | if [[ $line =~ (export const VERSION) ]] 8 | then 9 | VERSION=`tr '-' '_' <<< $result` 10 | VERSION=`tr '+' '_' <<< $VERSION` 11 | fi 12 | done < "$input" 13 | echo $VERSION 14 | export WORKBENCH_VERSION=$VERSION 15 | -------------------------------------------------------------------------------- /docker/ui/Dockerfile.ui: -------------------------------------------------------------------------------- 1 | # take react build and copy to nginx 2 | FROM nginx:alpine 3 | COPY nginx.conf /etc/nginx/conf.d/default.conf 4 | COPY /build /usr/share/nginx/html/ 5 | EXPOSE 80 6 | CMD ["nginx", "-g", "daemon off;"] 7 | -------------------------------------------------------------------------------- /docker/ui/build_labs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo ">>> Removing ./ui folder" 3 | ./cleanup_ui.sh $1 4 | echo ">>> Copying ./ui folder without node_modules" 5 | rsync -av --progress ../../ui . --exclude node_modules 6 | cd ui 7 | rm ./public/config/env-config.js 8 | rm ./.env.development* 9 | rm ./.env.my.development* 10 | rm ./.env.test* 11 | rm ./.env.production* 12 | rmdir ./public/config 13 | echo ">>> Doing Cypher Workbench Labs build" 14 | #Commenting out so the enterprise files remain 15 | #node build_basic.js 16 | if [ "$1" = "-skipNpmInstall" ] 17 | then 18 | echo ">>> Skipping npm install" 19 | mv ../keep/node_modules . 20 | else 21 | echo ">>> Doing npm install" 22 | npm install 23 | fi 24 | echo ">>> Copying .env file" 25 | cp ../ui.env ./.env 26 | echo ">>> Doing react build" 27 | npm run build 28 | rm -rf docker_context/ 29 | mkdir docker_context 30 | mv ./build docker_context 31 | cp nginx.conf docker_context 32 | cd .. 33 | echo ">>> Doing docker build" 34 | docker build -f Dockerfile.ui ./ui/docker_context -t cypher-workbench-ui:${WORKBENCH_VERSION}_labs 35 | -------------------------------------------------------------------------------- /docker/ui/cleanup_ui.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ "$1" = "-skipNpmInstall" ] 3 | then 4 | mkdir ./keep 5 | mv ./ui/node_modules ./keep 6 | find ./ui -exec rm -rdf "{}" \; 7 | else 8 | find ./ui -exec rm -rdf "{}" \; 9 | fi 10 | -------------------------------------------------------------------------------- /docker/workbench_labs_files/certficate/dhparam.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIICCAKCAgEA7UDMb0qXU4N895OjXkwQiHyPxYD0sgfXhBDMY6r/sarTuMfjksQ0 3 | bI/CJW2AGGXjrLsvKGQSextsr89nwpxDiqlgc8BrtxWoU1c/OkY/IFdD9vUnOERF 4 | by/Ry12roDw5lDbuVxCm0NR9kKwUnlt8ZTgYmurMXKXy+hmUBerZo/SYb/3eWkib 5 | HwySfwE0ITBJus3yRhL5e5boxv1TkcFwEamBRpg2DtNG1BQyBF41B0o4dPtUiFKE 6 | tUNYDfhkjDQRlL/C3aD8DacHvSWjEEy8dmVRDhGSjCcvGEnthGesF3LA3wYkD08n 7 | blFEZhEWoAT3oy5CZUw2xp529sIB/GtXsplW7RUTFacB2xNUrkTv8hzEBgc5Id0/ 8 | 1Zwyfm8TiprAs8GlWtk995jM7KIMPxYX8gEBmhBTQ9TTJ6FrhmxyeICELsVNEPK0 9 | E+aZEmt+0T1106qxy0a3AZ0lp1sQ0pR6gPjYrtCn9gews/AwZ4xg4amIE/OHIsY8 10 | vb3UtejSO0vCCaIJnJUxnTKXiZ56HuV/M9pn1YGNHqKcRnV/DrWS+Up5uK7jAWQX 11 | I0HFNpwor6FyfKBX0VgCGMJ8p9bnOQabqC9a/0leWyEuTyz9MjchsMJ6V4sPKgF+ 12 | Htua4IWrlM57tkGmeHMzS8FxqNWMyXH5Pp2NX8ar7bxx/ZQFi5N8UKMCAQI= 13 | -----END DH PARAMETERS----- 14 | -------------------------------------------------------------------------------- /docker/workbench_labs_files/certficate/workbench.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDnjCCAoYCCQChPfUo2FnY3TANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMC 3 | VVMxCzAJBgNVBAgMAk1EMRIwEAYDVQQHDAlGcmVkZXJpY2sxDjAMBgNVBAoMBU5l 4 | bzRqMRIwEAYDVQQLDAlTb2x1dGlvbnMxGDAWBgNVBAMMD25lbzRqLnNvbHV0aW9u 5 | czEiMCAGCSqGSIb3DQEJARYTc29sdXRpb25zQG5lbzRqLmNvbTAeFw0yMzA4MDcy 6 | MTM0MTNaFw0zMzA4MDQyMTM0MTNaMIGQMQswCQYDVQQGEwJVUzELMAkGA1UECAwC 7 | TUQxEjAQBgNVBAcMCUZyZWRlcmljazEOMAwGA1UECgwFTmVvNGoxEjAQBgNVBAsM 8 | CVNvbHV0aW9uczEYMBYGA1UEAwwPbmVvNGouc29sdXRpb25zMSIwIAYJKoZIhvcN 9 | AQkBFhNzb2x1dGlvbnNAbmVvNGouY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 10 | MIIBCgKCAQEA1rQq46tDwSWPsomp+PhOE0XNlCNdEKHmokHcBHSfYiPUSSjXlUu0 11 | cv4quCYUJLHoPR9RuwqjbI/DWLkfx25gzm008KDb/Q5XWygBM7vX/vA5AnaATnvY 12 | mXR7c08pJrtAFl2a9R0ssKJdwS6Cj28MfEFlfSAZ7ATA1HFmtF1Z7TOfDRq4BGIV 13 | I8BStGNFBGpa/xfkj5S066dK97NQZJyc9sc1L7fBLCdjcvehwfxzUh8rZXwoZAu+ 14 | Q6qj7e9vEuEbtSDD4kqSdUvLeTbMBKBsk5fydodBHmeInFbfi3M2F5MXoVtThpoA 15 | kk8ZjcMFiREBTIE9ZDzLLGbheN/QfW2qzQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB 16 | AQCgqyjW8s3WKLdDPFPtmt+KBC9ztAWEMArc3b7O6O3z1OV4SxXx4/c6t1+vApUl 17 | jns/DESeIy42zk1+84Z6lssm1gYCOmurrP+aDNaqx77GsXk52yI6DuutOBxBcAuv 18 | Trb95ehDcGt928w5UKyCAmBN9Bf1kMBWPMw52l9BFUIGWb8txSBNUV2GMIU0FvFp 19 | yRY6fhxAwMJL2YJj1QN61I/GlGeHrTsthWvVfNznQqzIy8KiQIQc+x2ls9ewVPck 20 | BY3HzHWesFU8Nlg1Nj2dzQiJL72hQ64SrGA5KcJoI6PkYh3zzfTlqrWZD9+P2hdu 21 | Bo4a4UNAGYfmdbO9J5W+8FWy 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /docker/workbench_labs_files/certficate/workbench.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDWtCrjq0PBJY+y 3 | ian4+E4TRc2UI10QoeaiQdwEdJ9iI9RJKNeVS7Ry/iq4JhQkseg9H1G7CqNsj8NY 4 | uR/HbmDObTTwoNv9DldbKAEzu9f+8DkCdoBOe9iZdHtzTykmu0AWXZr1HSywol3B 5 | LoKPbwx8QWV9IBnsBMDUcWa0XVntM58NGrgEYhUjwFK0Y0UEalr/F+SPlLTrp0r3 6 | s1BknJz2xzUvt8EsJ2Ny96HB/HNSHytlfChkC75DqqPt728S4Ru1IMPiSpJ1S8t5 7 | NswEoGyTl/J2h0EeZ4icVt+LczYXkxehW1OGmgCSTxmNwwWJEQFMgT1kPMssZuF4 8 | 39B9barNAgMBAAECggEAUw+8j/uP6McpQn7znPgi7F3S3H6Z4HdiDF/CSI79ojAO 9 | TodYb8+r7djamgdRP6j8GblmGyBQfQ1ZG5fy7WqMVQ/2rSjuMoY70W42mNcRBvXU 10 | eFlz5ekPsL5ZjBCR8QP8FSr9adpxkZwe/T+LDwZ9JA0AwFmM7bwhBY8hlqGOyq66 11 | Za0iJ1aMbKPJX8+9g0NPMe/LDGxgGY1kyiuFobfRNg1rAq38EVjEucYdShpB0V+e 12 | TluaN07Wk2kJg20qgXPXhLdmdNOlIZIL8dNbD0hQ/HwIper38Uu5257T7IIiDF3u 13 | OKXoDW8tiUqwEdZaHvEIfWPvzoxF+ZQXqICSI5+8wQKBgQDvvLhIcow98pR3ueWh 14 | HLhCSSQcBURbOmju0X82BDHY2elr+/Zl+GRjcDsG8Od8x2F31ToSX1DbyQXXYSDo 15 | RlUEUCNHGbWCnjiqfWd1DCQYC3ubrefIxGUpO9WNLPGsAIgtvKIrLvEOgukqDoSH 16 | ujBLm+nvS4cOwhBnC19MwL/GWQKBgQDlRLeTXnRdxf/VC6T8Lu6X4VwJCbQqDila 17 | zPZ+NBi/prRgnb5rqsbqvaoi+6fQpDYfLYfFF3rKOTgLcydHtOBUx13H8YjnzaHY 18 | WM+DNZljALMD/RIc+IkyVKTy26XiEgjjZ8LdJXxJZQn1RGohlsmIXr2ACBG28bz9 19 | rt4rCvPhlQKBgDnZKgahTWHtVRIG03gq+/NSvtncE4CH+aYW+0FHhdezzXV3GR/F 20 | 7kNoY3XrT5B2c/h8hUTFpzdBrJ0qHMyvm/gsdjbD516bW0UYeYxu347Fxo/sSM/T 21 | RC3M+FzWiYJdpn6S0/bjnttHj4fMdQJjVSAJgUtyyCYxgc+7mMVmhWARAoGBANCe 22 | MLTmM9jIPDyttdjLE8wcAlUvAUNrU3IOIxU/bm2l0WhA4W6zHQGox9HBUDhn09+W 23 | 3H4ZGWA9pKO2ir2S9rXuG4W+YKcc0/I7DcgE06fkkQBGHV9DQAQORXG/MDh/1Jqo 24 | ZgY4/9kBGYiWUkRyIrv2CVUhAo2HdkMYBY0BEF3pAoGBAJnNhpuugvMVD+U/vFoY 25 | SvfotbEbUODnPJDJmVYjbONpafelBUGLiNW80IiXN8H9yBwRb+TEnbxtXL1bUh/E 26 | x8iES223oumnO0v5VwAZ86P80A30roP3ox2LvYTfwkPiN5IsIdgst8cZPnLGBkSk 27 | /G3Gn/Aee532Jfb+kPumjAZK 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/workbench_labs_files/envConfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Setting WORKBENCH_PROTOCOL, WORKBENCH_HOST, WORKBENCH_PORT_EXTERNAL, WORKBENCH_PORT_INTERNAL, GRAPHQL_PROTOCOL, GRAPHQL_HOST, and GRAPHQL_PORT - modify envConfig.sh to change" 3 | 4 | WORKBENCH_PROTOCOL=http 5 | WORKBENCH_HOST=localhost 6 | WORKBENCH_PORT_EXTERNAL=80 7 | WORKBENCH_PORT_INTERNAL=80 8 | 9 | # for accessing graphql via react-ui-hostname/graphql via configured nginx proxy 10 | # use the internal docker-compose networking names and ports 11 | GRAPHQL_PROTOCOL=http 12 | GRAPHQL_HOST=cypher-workbench-api 13 | GRAPHQL_PORT=4000 14 | 15 | export WORKBENCH_PROTOCOL 16 | export WORKBENCH_HOST 17 | export WORKBENCH_PORT_EXTERNAL 18 | export WORKBENCH_PORT_INTERNAL 19 | export GRAPHQL_PROTOCOL 20 | export GRAPHQL_HOST 21 | export GRAPHQL_PORT -------------------------------------------------------------------------------- /docker/workbench_labs_files/makeEnv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cat /dev/null > .env 3 | 4 | echo "Creating docker-compose .env file" 5 | 6 | # variables are defined in envConfig.sh 7 | echo "WORKBENCH_PROTOCOL=$WORKBENCH_PROTOCOL" >> .env 8 | echo "WORKBENCH_HOST=$WORKBENCH_HOST" >> .env 9 | echo "WORKBENCH_PORT_EXTERNAL=$WORKBENCH_PORT_EXTERNAL" >> .env 10 | echo "WORKBENCH_PORT_INTERNAL=$WORKBENCH_PORT_INTERNAL" >> .env 11 | echo "GRAPHQL_PROTOCOL=$GRAPHQL_PROTOCOL" >> .env 12 | echo "GRAPHQL_HOST=$GRAPHQL_HOST" >> .env 13 | echo "GRAPHQL_PORT=$GRAPHQL_PORT" >> .env 14 | -------------------------------------------------------------------------------- /docker/workbench_labs_files/makeEnvConfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Creating env-config.js file" 3 | # environment variables should be set in envConfig.sh 4 | 5 | sed 's|${WORKBENCH_PROTOCOL}|'$WORKBENCH_PROTOCOL'|g' $PWD/cw-config/cw-ui-template/env-config.js.template > $PWD/cw-config/cw-ui/env-config.js.1 6 | sed 's|${WORKBENCH_HOST}|'$WORKBENCH_HOST'|g' $PWD/cw-config/cw-ui/env-config.js.1 > $PWD/cw-config/cw-ui/env-config.js.2 7 | sed 's|${WORKBENCH_PORT}|'$WORKBENCH_PORT_EXTERNAL'|g' $PWD/cw-config/cw-ui/env-config.js.2 > $PWD/cw-config/cw-ui/env-config.js 8 | rm $PWD/cw-config/cw-ui/env-config.js.1 9 | rm $PWD/cw-config/cw-ui/env-config.js.2 10 | -------------------------------------------------------------------------------- /docker/workbench_labs_files/makeNginxConf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Creating nginx.conf file" 3 | # environment variables should be set in envConfig.sh 4 | #envsubst '${GRAPHQL_PROTOCOL} ${GRAPHQL_PORT} ${GRAPHQL_HOST}' < ./cw-config/cw-ui-nginx-template/nginx.conf.template > ./cw-config/cw-ui-nginx/nginx.conf 5 | #using sed three times instead of envsubst because envsubst not available on mac by default 6 | sed 's|${GRAPHQL_PROTOCOL}|'$GRAPHQL_PROTOCOL'|g' $PWD/cw-config/cw-ui-nginx-template/nginx.conf.template > $PWD/cw-config/cw-ui-nginx/nginx.conf.1 7 | sed 's|${GRAPHQL_HOST}|'$GRAPHQL_HOST'|g' $PWD/cw-config/cw-ui-nginx/nginx.conf.1 > $PWD/cw-config/cw-ui-nginx/nginx.conf.2 8 | sed 's|${GRAPHQL_PORT}|'$GRAPHQL_PORT'|g' $PWD/cw-config/cw-ui-nginx/nginx.conf.2 > $PWD/cw-config/cw-ui-nginx/nginx.conf 9 | rm $PWD/cw-config/cw-ui-nginx/nginx.conf.1 10 | rm $PWD/cw-config/cw-ui-nginx/nginx.conf.2 -------------------------------------------------------------------------------- /docker/workbench_labs_files/workbench_labs: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | doExit () { 3 | echo >&2 "$@" 4 | exit 1 5 | } 6 | 7 | [ "$#" -eq 1 ] || doExit "Usage: workbench (start | stop | status | unpack)" 8 | if [ "$1" = "start" ] 9 | then 10 | echo "Backing up envConfig.sh to file envConfig.sh.bak" 11 | cp envConfig.sh envConfig.sh.bak 12 | echo "Backing up docker-compose.yml to file docker-compose.yml.bak" 13 | cp docker-compose.yml docker-compose.yml.bak 14 | echo "Backing up ./cw-config/cw-ui/env-config.js to file ./cw-config/cw-ui/env-config.js.bak" 15 | cp ./cw-config/cw-ui/env-config.js ./cw-config/cw-ui/env-config.js 16 | echo "Backing up ./cw-config/cw-ui-nginx/nginx.conf to file ./cw-config/cw-ui-nginx/nginx.conf.bak" 17 | cp ./cw-config/cw-ui-nginx/nginx.conf ./cw-config/cw-ui-nginx/nginx.conf.bak 18 | echo "Starting Cypher Workbench services" 19 | source ./envConfig.sh 20 | source ./makeEnv.sh 21 | ./makeEnvConfig.sh 22 | ./makeNginxConf.sh 23 | docker-compose --project-name cypher-workbench up -d --remove-orphans 24 | echo "Cypher Workbench should be available at $WORKBENCH_PROTOCOL://$WORKBENCH_HOST:$WORKBENCH_PORT_EXTERNAL in a few minutes" 25 | elif [ "$1" = "stop" ] 26 | then 27 | echo "Stopping Cypher Workbench services" 28 | docker-compose --project-name cypher-workbench stop 29 | elif [ "$1" = "status" ] 30 | then 31 | echo "Cypher Workbench status" 32 | docker ps | grep cypher-workbench 33 | elif [ "$1" = "unpack" ] 34 | then 35 | if [ -d "./cw-config" ] 36 | then 37 | echo "*** Config folder exists. Skipping unpacking of config ***" 38 | else 39 | echo "" 40 | echo "*** Unpacking config ***" 41 | echo "" 42 | tar xvfz cw-config.tar.gz 43 | fi 44 | echo "" 45 | echo "*** Unpacking docker images ***" 46 | echo "" 47 | docker load < workbenchDocker.tar.gz 48 | echo "" 49 | fi 50 | -------------------------------------------------------------------------------- /ui/.env.example: -------------------------------------------------------------------------------- 1 | # Address of API GraphQL endpoint 2 | REACT_APP_GRAPHQL_URI=http://localhost:4000/graphql 3 | 4 | # run mode, valid values are 'normal' and 'partner' 5 | REACT_APP_RUN_MODE=normal 6 | 7 | # URL of the UI app 8 | REACT_APP_BASE_URL=http://localhost:3000 9 | 10 | # authentication scheme, valid values are 'local' and 'auth0' 11 | REACT_APP_AUTH_METHOD=local 12 | 13 | # IMPORTANT: change this value before creating any users 14 | # make sure this matches the value of ENCRYPTION_KEY in api/.env 15 | REACT_APP_ENCRYPTION_KEY=workbenchEncryptionKey 16 | 17 | # external help url 18 | REACT_APP_HELP_URL=https://help.neo4j.solutions/neo4j-solutions/cypher-workbench/ 19 | 20 | # enable early prototype graphical debugger 21 | REACT_APP_CYPHER_CANVAS_DEBUG_ENABLED=true 22 | 23 | # EULA info 24 | REACT_APP_EULA=none 25 | 26 | -------------------------------------------------------------------------------- /ui/.env.example.extrasettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | # instead of normal 4 | #REACT_APP_RUN_MODE=partner 5 | 6 | # instead of local 7 | #REACT_APP_AUTH_METHOD=auth0 8 | 9 | # Auth0 settings - if Auth0 configured 10 | # REACT_APP_AUTH_DOMAIN=.auth0.com 11 | # REACT_APP_AUTH_CLIENT_ID= 12 | 13 | #REACT_APP_AUTH_CALLBACK=http://localhost:3000/callback 14 | #REACT_APP_AUTH_AUTH0_LOGOUT_URL=http%3A%2F%2Flocalhost:3000/login 15 | 16 | # Neo4j Hive settings - if configured 17 | # REACT_APP_HIVE_URI=http:///graphql 18 | # REACT_APP_HIVE_UI=https:///solutions 19 | # REACT_APP_SOLUTION= 20 | # REACT_APP_SOLUTION_BASE_URL_IN_HIVE=http:// 21 | 22 | # Old Eula settings 23 | #REACT_APP_EULA=eula 24 | #REACT_APP_EULA_GET_USER=https:///user 25 | #REACT_APP_EULA_UPDATE_METADATA=https:///update 26 | #REACT_APP_IGNORE_EULA_FOR_EMAIL_DOMAINS=neotechnology.com,neo4j.com,neo4j.org 27 | #REACT_APP_EULA_TIMESTAMP=1588184359452 28 | 29 | # Old Keymaker settings 30 | #REACT_APP_KEYMAKER_GRAPHQL_URI=http:///graphql 31 | #REACT_APP_ADMIN_APP_GRAPHQL_URI=http:///graphql 32 | 33 | # Segment 34 | #REACT_APP_SEGMENT_API_KEY= 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ui/Dockerfile: -------------------------------------------------------------------------------- 1 | # stage 1 - build environment 2 | FROM node:18.12.1 as react-build 3 | RUN mkdir -p /app 4 | WORKDIR /app 5 | COPY . ./ 6 | RUN npm install --legacy-peer-deps 7 | RUN npm run build 8 | 9 | # stage 2 - production environment 10 | FROM nginx:alpine 11 | COPY nginx.conf /etc/nginx/conf.d/default.conf 12 | COPY --from=react-build /app/build /usr/share/nginx/html/ 13 | EXPOSE 80 14 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /ui/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | #charset koi8-r; 5 | #access_log /var/log/nginx/host.access.log main; 6 | 7 | location / { 8 | root /usr/share/nginx/html; 9 | index index.html index.htm; 10 | try_files $uri /index.html; 11 | } 12 | 13 | #location /graphql { 14 | # proxy_pass http://${GRAPHQL_HOST}:${GRAPHQL_PORT}/graphql; 15 | #} 16 | 17 | # EM: see article here for more info: 18 | # https://serverfault.com/questions/577370/how-can-i-use-environment-variables-in-nginx-conf 19 | 20 | #error_page 404 /404.html; 21 | 22 | # redirect server error pages to the static page /50x.html 23 | # 24 | error_page 500 502 503 504 /50x.html; 25 | location = /50x.html { 26 | root /usr/share/nginx/html; 27 | } 28 | } -------------------------------------------------------------------------------- /ui/public/boltWrapper.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 24 | HTTP Bolt Wrapper 25 | 26 | 27 | HTTP Bolt Wrapper 28 | 29 | 30 | -------------------------------------------------------------------------------- /ui/public/config/env-config.js: -------------------------------------------------------------------------------- 1 | 2 | window._dynamicEnv_ = { 3 | REACT_APP_MODEL_DATA_EXPORT_ENABLED: false, 4 | REACT_APP_CYPHER_SUITE_EXPORT_TO_BIGQUERY_ENABLED: false, 5 | } 6 | 7 | -------------------------------------------------------------------------------- /ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo4j-labs/cypher-workbench/1689899a882edc793d30603fbb31fcb0ce1e63c4/ui/public/favicon.ico -------------------------------------------------------------------------------- /ui/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo4j-labs/cypher-workbench/1689899a882edc793d30603fbb31fcb0ce1e63c4/ui/public/favicon.png -------------------------------------------------------------------------------- /ui/public/favicon_react.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo4j-labs/cypher-workbench/1689899a882edc793d30603fbb31fcb0ce1e63c4/ui/public/favicon_react.ico -------------------------------------------------------------------------------- /ui/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo4j-labs/cypher-workbench/1689899a882edc793d30603fbb31fcb0ce1e63c4/ui/public/logo192.png -------------------------------------------------------------------------------- /ui/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo4j-labs/cypher-workbench/1689899a882edc793d30603fbb31fcb0ce1e63c4/ui/public/logo512.png -------------------------------------------------------------------------------- /ui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /ui/public/neo4j-logo-white-RGB-transBG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo4j-labs/cypher-workbench/1689899a882edc793d30603fbb31fcb0ce1e63c4/ui/public/neo4j-logo-white-RGB-transBG.png -------------------------------------------------------------------------------- /ui/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /ui/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | 35 | .MuiTab-wrapper { 36 | align-items: initial !important; 37 | } 38 | 39 | .MuiIcon-root { 40 | width: 1.5em !important; 41 | } 42 | 43 | .MuiSelect-icon { 44 | margin-top: .2em !important; 45 | } 46 | 47 | .MuiSelect-selectMenu { 48 | margin-top: .5em !important; 49 | } 50 | 51 | 52 | /* https://stackoverflow.com/questions/826782/how-to-disable-text-selection-highlighting */ 53 | .noselect { 54 | -webkit-touch-callout: none; /* iOS Safari */ 55 | -webkit-user-select: none; /* Safari */ 56 | -khtml-user-select: none; /* Konqueror HTML */ 57 | -moz-user-select: none; /* Old versions of Firefox */ 58 | -ms-user-select: none; /* Internet Explorer/Edge */ 59 | user-select: none; /* Non-prefixed version, currently 60 | supported by Chrome, Opera and Firefox */ 61 | } 62 | -------------------------------------------------------------------------------- /ui/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | /* 6 | it('renders without crashing', () => { 7 | const div = document.createElement('div'); 8 | ReactDOM.render(, div); 9 | ReactDOM.unmountComponentAtNode(div); 10 | }); 11 | */ 12 | 13 | it('has a test', () => { 14 | expect(true).toBe(true); 15 | }) 16 | -------------------------------------------------------------------------------- /ui/src/auth/authUtil.js: -------------------------------------------------------------------------------- 1 | 2 | import { getDynamicConfigValue } from '../dynamicConfig'; 3 | 4 | import auth0 from "./auth0"; 5 | import localAuth from "./localAuth"; 6 | 7 | var authMethod = null; 8 | var auth = null; 9 | 10 | // the processing behaviour changed somehow where constants defined at top of the 11 | // file dependent on config values were not being set before a call to getClient(). 12 | // logic has been changed to set them on first request 13 | export const getAuthMethod = () => { 14 | if (!authMethod) { 15 | authMethod = getDynamicConfigValue("REACT_APP_AUTH_METHOD"); 16 | } 17 | return authMethod; 18 | } 19 | 20 | export const getAuth = () => { 21 | if (!auth) { 22 | auth = (getAuthMethod() === "auth0") ? auth0 : localAuth; 23 | } 24 | return auth; 25 | } -------------------------------------------------------------------------------- /ui/src/auth/callback.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import auth from "./auth0"; 3 | import { withRouter } from "react-router-dom"; 4 | import { doRedirect } from "./history"; 5 | import { getDynamicConfigValue } from '../dynamicConfig'; 6 | import { getSystemMessages } from '../persistence/graphql/GraphQLPersistence'; 7 | 8 | class Callback extends Component { 9 | async componentDidMount() { 10 | try { 11 | await auth.handleAuthentication(); 12 | var systemMessages = await getSystemMessages(); 13 | if (systemMessages.success && systemMessages.messages && systemMessages.messages.length > 0) { 14 | doRedirect("/messages"); 15 | } 16 | 17 | doRedirect("/"); 18 | } catch (err) { 19 | console.log(err); 20 | if (err && err.errorDescription) { 21 | alert(err.errorDescription); 22 | } else { 23 | alert('' + err); 24 | } 25 | auth.logout(); 26 | } 27 | } 28 | 29 | render() { 30 | const style = { 31 | position: "absolute", 32 | display: "flex", 33 | justifyContent: "center", 34 | height: "100vh", 35 | width: "100vw", 36 | top: 0, 37 | bottom: 0, 38 | left: 0, 39 | right: 0, 40 | backgroundColor: "white" 41 | }; 42 | 43 | return
; 44 | } 45 | } 46 | 47 | export default withRouter(Callback); 48 | -------------------------------------------------------------------------------- /ui/src/auth/history.js: -------------------------------------------------------------------------------- 1 | import { isDebuggingEnabled, getUrlQueryString, printDebug } from "../App"; 2 | 3 | const history = require("history").createBrowserHistory; 4 | const appHistory = history({ forceRefresh: true }); 5 | 6 | export const doRedirect = (redirect, message) => { 7 | var url = redirect; 8 | if (isDebuggingEnabled()) { 9 | url += getUrlQueryString(); 10 | } 11 | message = (message) ? `${message}: redirect` : 'redirect'; 12 | printDebug(`${message}: ${url}`); 13 | appHistory.replace(url); 14 | } 15 | 16 | export default appHistory; -------------------------------------------------------------------------------- /ui/src/auth/privateRoute.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route } from "react-router-dom"; 3 | 4 | const PrivateRoute = ({ auth, path, component: Component, ...rest }) => { 5 | return ( 6 | { 12 | if (auth.type === "auth0") { 13 | if (auth.isAuthenticated()) { 14 | //console.log('props: ', props); 15 | return ; 16 | } else { 17 | return <>You are not currently authenticated; 18 | } 19 | } else if (auth.type === "local") { 20 | if (auth.isAuthenticated()) { 21 | return ; 22 | } else if ( 23 | props.location.pathname === "/login" || 24 | props.location.pathname.match("/eula/*") 25 | ) { 26 | return <>You are not currently authenticated; 27 | } 28 | } else { 29 | return <>{`The application auth method ${auth.type} is not supported`}; 30 | } 31 | }} 32 | /> 33 | ) 34 | } 35 | export default PrivateRoute; 36 | -------------------------------------------------------------------------------- /ui/src/common/LicensedFeatures.test.js: -------------------------------------------------------------------------------- 1 | 2 | import { setLicensedFeatures, anyFeatureLicensed } from './LicensedFeatures'; 3 | 4 | test('any feature licensed', () => { 5 | setLicensedFeatures(['A','B','C']); 6 | expect(anyFeatureLicensed(['A'])).toBe(true); 7 | expect(anyFeatureLicensed(['C','E'])).toBe(true); 8 | expect(anyFeatureLicensed(['D','E'])).toBe(false); 9 | }); 10 | -------------------------------------------------------------------------------- /ui/src/common/LicensedFeaturesCheck.js: -------------------------------------------------------------------------------- 1 | 2 | import { getLicensedFeatures } from './LicensedFeatures'; 3 | 4 | export const isFeatureLicensedEx = (feature) => { 5 | return getLicensedFeatures().has(feature); 6 | } 7 | 8 | export const anyFeatureLicensedEx = (features) => { 9 | const licensedFeatures = getLicensedFeatures(); 10 | return (features.find(x => licensedFeatures.has(x))) ? true : false; 11 | } 12 | -------------------------------------------------------------------------------- /ui/src/common/css/react-resizable.css: -------------------------------------------------------------------------------- 1 | .react-resizable { 2 | position: relative; 3 | } 4 | .react-resizable-handle { 5 | position: absolute; 6 | width: 20px; 7 | height: 20px; 8 | background-repeat: no-repeat; 9 | background-origin: content-box; 10 | box-sizing: border-box; 11 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+'); 12 | background-position: bottom right; 13 | padding: 0 3px 3px 0; 14 | } 15 | .react-resizable-handle-sw { 16 | bottom: 0; 17 | left: 0; 18 | cursor: sw-resize; 19 | transform: rotate(90deg); 20 | } 21 | .react-resizable-handle-se { 22 | bottom: 0; 23 | right: 0; 24 | cursor: se-resize; 25 | } 26 | .react-resizable-handle-nw { 27 | top: 0; 28 | left: 0; 29 | cursor: nw-resize; 30 | transform: rotate(180deg); 31 | } 32 | .react-resizable-handle-ne { 33 | top: 0; 34 | right: 0; 35 | cursor: ne-resize; 36 | transform: rotate(270deg); 37 | } 38 | .react-resizable-handle-w, 39 | .react-resizable-handle-e { 40 | top: 50%; 41 | margin-top: -10px; 42 | cursor: ew-resize; 43 | } 44 | .react-resizable-handle-w { 45 | left: 0; 46 | transform: rotate(135deg); 47 | } 48 | .react-resizable-handle-e { 49 | right: 0; 50 | transform: rotate(315deg); 51 | } 52 | .react-resizable-handle-n, 53 | .react-resizable-handle-s { 54 | left: 50%; 55 | margin-left: -10px; 56 | cursor: ns-resize; 57 | } 58 | .react-resizable-handle-n { 59 | top: 0; 60 | transform: rotate(225deg); 61 | } 62 | .react-resizable-handle-s { 63 | bottom: 0; 64 | transform: rotate(45deg); 65 | } -------------------------------------------------------------------------------- /ui/src/common/eula/eulaHelper.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | getLicenseInfo 4 | } from '../../persistence/graphql/GraphQLPersistence'; 5 | import { 6 | LICENSE_TYPES 7 | } from '../LicensedFeatures'; 8 | 9 | export const getEulaFile = async () => { 10 | var licenseInfo = await getLicenseInfo(); 11 | if (licenseInfo.success) { 12 | licenseInfo = licenseInfo.licenseInfo; 13 | 14 | } else { 15 | //alert(`Error fetching license info: ${licenseInfo.error}`); 16 | console.log(`Error fetching license info: ${licenseInfo.error}`); 17 | return; 18 | } 19 | //console.log('licenseInfo: ', licenseInfo); 20 | var licenseEulaFile = 'CypherWorkbenchEULA.html'; 21 | switch (licenseInfo.type) { 22 | case LICENSE_TYPES.Basic: 23 | case LICENSE_TYPES.Cloud_Basic: 24 | licenseEulaFile = 'CypherWorkbenchEULA_Basic.html' 25 | //console.log('promise resolving License file is: ', licenseEulaFile); 26 | break; 27 | case LICENSE_TYPES.Enterprise: 28 | case LICENSE_TYPES.Cloud_Enterprise: 29 | licenseEulaFile = 'CypherWorkbenchEULA_Enterprise.html' 30 | //console.log('promise resolving License file is: ', licenseEulaFile); 31 | break; 32 | case LICENSE_TYPES.EnterpriseTrial: 33 | case LICENSE_TYPES.Cloud_EnterpriseTrial: 34 | licenseEulaFile = 'CypherWorkbenchEULA_EnterpriseTrial.html' 35 | //console.log('promise resolving License file is: ', licenseEulaFile); 36 | break; 37 | } 38 | //console.log('License file is: ', licenseEulaFile); 39 | return licenseEulaFile; 40 | } -------------------------------------------------------------------------------- /ui/src/common/parse/antlr/AutocompleteErrorListener.js: -------------------------------------------------------------------------------- 1 | // from article https://medium.com/dailyjs/compiler-in-javascript-using-antlr-9ec53fd2780f 2 | // and https://blog.rapid7.com/2015/06/29/how-to-implement-antlr4-autocomplete/ 3 | //import antlr4 from 'antlr4/src/antlr4'; 4 | //import antlr4 from 'antlr4/src/antlr4/index'; 5 | import antlr4 from 'antlr4'; 6 | import { processTokens } from '../parseUtil'; 7 | 8 | /** 9 | * Autocomplete Error Listener 10 | * 11 | * @returns {object} 12 | */ 13 | export class AutocompleteErrorListener extends antlr4.error.ErrorListener { 14 | 15 | constructor (originalCypher) { 16 | super(); 17 | this.errorOccurred = false; 18 | this.tokens = []; 19 | this.originalCypher = originalCypher; 20 | if (this.originalCypher && this.originalCypher.split) { 21 | this.cypherLines = this.originalCypher.split('\n'); 22 | } 23 | } 24 | 25 | /** 26 | * Checks syntax error 27 | * 28 | * @param {object} recognizer The parsing support code essentially. Most of it is error recovery stuff 29 | * @param {object} symbol Offending symbol 30 | * @param {int} line Line of offending symbol 31 | * @param {int} column Position in line of offending symbol 32 | * @param {string} message Error message 33 | * @param {string} payload Stack trace 34 | */ 35 | syntaxError = (recognizer, symbol, line, column, message, payload) => { 36 | // https://blog.rapid7.com/2015/06/29/how-to-implement-antlr4-autocomplete/ 37 | 38 | this.errorOccurred = true; 39 | 40 | var parser = recognizer._ctx.parser; 41 | var tokens = parser.getTokenStream().tokens; 42 | 43 | this.tokens = processTokens(tokens, parser, this.cypherLines, { leaveSpaces: true }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ui/src/common/parse/antlr/ErrorListener.js: -------------------------------------------------------------------------------- 1 | // from article https://medium.com/dailyjs/compiler-in-javascript-using-antlr-9ec53fd2780f 2 | //import antlr4 from 'antlr4/src/antlr4'; 3 | import antlr4 from 'antlr4'; 4 | 5 | export class SyntaxGenericError extends Error { 6 | constructor(syntaxObject) { 7 | super("Syntax error"); 8 | this.name = "SyntaxGenericError"; 9 | this.symbol = syntaxObject.symbol; 10 | //Object.keys(syntaxObject.symbol).map(key => console.log('symbol key ' + key + ' = ' + syntaxObject.symbol[key])); 11 | this.line = syntaxObject.line; 12 | this.column = syntaxObject.column; 13 | //this.message = `Unexpected symbol ${this.symbol}, line: ${this.line}, column: ${this.column}, message: ${syntaxObject.message}`; 14 | this.message = syntaxObject.message; 15 | } 16 | } 17 | 18 | /** 19 | * Custom Error Listener 20 | * 21 | * @returns {object} 22 | */ 23 | export class CustomErrorListener extends antlr4.error.ErrorListener { 24 | /** 25 | * Checks syntax error 26 | * 27 | * @param {object} recognizer The parsing support code essentially. Most of it is error recovery stuff 28 | * @param {object} symbol Offending symbol 29 | * @param {int} line Line of offending symbol 30 | * @param {int} column Position in line of offending symbol 31 | * @param {string} message Error message 32 | * @param {string} payload Stack trace 33 | */ 34 | syntaxError(recognizer, symbol, line, column, message, payload) { 35 | throw new SyntaxGenericError({symbol, line, column, message}); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ui/src/common/parse/antlr/antlr-4.10.1-complete.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo4j-labs/cypher-workbench/1689899a882edc793d30603fbb31fcb0ce1e63c4/ui/src/common/parse/antlr/antlr-4.10.1-complete.jar -------------------------------------------------------------------------------- /ui/src/common/parse/antlr/commentUtil.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | export const skipIfComment = (stringToWorkOn, startIndex) => { 5 | if (startIndex < (stringToWorkOn.length - 1)) { 6 | let firstChar = stringToWorkOn[startIndex]; 7 | if (firstChar === '/') 8 | { 9 | let secondChar = stringToWorkOn[startIndex+1]; 10 | if (secondChar === '/') { 11 | return handleSingleLineComment(stringToWorkOn, startIndex+2); 12 | } else if (secondChar === '*') { 13 | return handleMultiLineComment(stringToWorkOn, startIndex+2); 14 | } 15 | } 16 | } 17 | return startIndex; 18 | } 19 | 20 | export const handleSingleLineComment = (stringToWorkOn, startIndex) => { 21 | // comment will end at a \n or end-of-string 22 | let index = stringToWorkOn.indexOf('\n', startIndex); 23 | if (index === -1) { 24 | return stringToWorkOn.length; 25 | } else { 26 | return index+1; // advance the index past the new line 27 | } 28 | } 29 | 30 | export const handleMultiLineComment = (stringToWorkOn, startIndex) => { 31 | // comment will end at an asterisk * and a slash / 32 | let index = stringToWorkOn.indexOf('*/', startIndex); 33 | if (index === -1) { 34 | return stringToWorkOn.length; 35 | } else { 36 | return index+2; // advance the index past the asterisk and slash 37 | } 38 | } 39 | 40 | export const stripComments = (stringToWorkOn) => { 41 | if (typeof(stringToWorkOn) === 'string') { 42 | let returnStr = ''; 43 | for (let index = 0; index < stringToWorkOn.length; index++) { 44 | index = skipIfComment(stringToWorkOn, index); 45 | if (index >= stringToWorkOn.length) { 46 | break; 47 | } 48 | returnStr += stringToWorkOn[index]; 49 | } 50 | return returnStr; 51 | } else { 52 | return stringToWorkOn; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ui/src/common/parse/antlr/parseReadme.txt: -------------------------------------------------------------------------------- 1 | If you make changes to the Cypher.g4 Antlr grammar, you should run this command from the folder that contains the Cypher.g4 file 2 | 3 | This is the new way (from Jon Harris enhanced cypher-editor project): 4 | ### cypher-antlr4 5 | 6 | This package contains: 7 | 8 | - JavaScript code generated from an antlr4 g4 grammar file. 9 | - The antlr4 g4 grammar file. 10 | - An antlr 4 java jar file used to generate the code. 11 | 12 | The `antlrGenerate` script in `package.json` gives an example of how to generate the source code yourself. 13 | 14 | ### General usage of Antlr 15 | https://github.com/antlr/antlr4/blob/master/doc/javascript-target.md 16 | 17 | ### Testing 18 | For testing, I was getting this error a lot: 19 | 20 | SyntaxError: Cannot use import statement outside a module 21 | 22 | until I added this to the package.json from the suggestion of this article: https://github.com/facebook/create-react-app/issues/9938 23 | 24 | "jest": { 25 | "transform": { 26 | "^.+\\.[t|j]s?$": "babel-jest" 27 | }, 28 | "transformIgnorePatterns": ["node_modules/(?!@antlr4)/"] 29 | } 30 | 31 | 32 | This is the old way, but as of Antlr 4.7.1 this way no longer works: 33 | (error(31): ANTLR cannot generate Javascript code as of version 4.7.1) 34 | 35 | antlr4 -Dlanguage=JavaScript Cypher.g4 36 | 37 | 38 | You may need to change these lines: 39 | require('antlr4/index'); 40 | 41 | to this: 42 | require('antlr4/src/antlr4/index'); 43 | -------------------------------------------------------------------------------- /ui/src/common/parse/antlr/withVariable.js: -------------------------------------------------------------------------------- 1 | 2 | export class WithVariable { 3 | 4 | constructor (properties) { 5 | properties = properties || {}; 6 | var { 7 | key, 8 | expression, 9 | variable, 10 | } = properties; 11 | 12 | this.key = key; 13 | this.expression = expression; 14 | this.variable = variable; 15 | } 16 | 17 | isAliased = () => (this.expression && this.variable) ? true : false; 18 | isAsterisk = () => (this.expression === '*') ? true : false; 19 | 20 | isEqualTo = (variable) => variable 21 | && this.key === variable.key 22 | && this.expression === variable.expression 23 | && this.variable === variable.variable 24 | } -------------------------------------------------------------------------------- /ui/src/common/parse/cypherFunctionHelper.js: -------------------------------------------------------------------------------- 1 | 2 | import { cypherFunctions } from './allFunctions'; 3 | 4 | export class CypherFunctionHelper { 5 | 6 | functions = []; 7 | functionPrefixes = []; 8 | 9 | constructor () { 10 | //console.log(cypherFunctions); 11 | var functionLines = cypherFunctions.split('\n'); 12 | functionLines 13 | .map(x => x.trim()) 14 | .filter(x => x) 15 | .map(x => { 16 | if (!this.functions.includes(x)) { 17 | this.functions.push(x); 18 | } 19 | }); 20 | 21 | this.functions.sort(); 22 | 23 | this.functions 24 | .map(x => x.split('.')) 25 | .filter(tokens => tokens.length > 1) 26 | .map(tokens => { 27 | var prefix = ''; 28 | for (var i = 0; i < tokens.length - 1; i++) { 29 | if (prefix) prefix += '.'; 30 | prefix += tokens[i]; 31 | if (!this.functionPrefixes.includes(prefix)) { 32 | this.functionPrefixes.push(prefix); 33 | } 34 | } 35 | }); 36 | } 37 | 38 | getFunctions = () => this.functions.slice(); 39 | getFunctionPrefixes = () => this.functionPrefixes.slice(); 40 | } -------------------------------------------------------------------------------- /ui/src/common/parse/cypherFunctionHelper.test.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | CypherFunctionHelper 4 | } from './cypherFunctionHelper'; 5 | 6 | test('test functions', () => { 7 | const functionHelper = new CypherFunctionHelper(); 8 | const functions = functionHelper.getFunctions(); 9 | 10 | // spot checks 11 | expect(functions.includes('count')).toBe(true); 12 | expect(functions.includes('abs')).toBe(true); 13 | expect(functions.includes('reduce')).toBe(true); 14 | }); 15 | 16 | 17 | test('test function prefixes', () => { 18 | const functionHelper = new CypherFunctionHelper(); 19 | const prefixes = functionHelper.getFunctionPrefixes(); 20 | 21 | // spot checks 22 | expect(prefixes.includes('apoc')).toBe(true); 23 | expect(prefixes.includes('apoc.coll')).toBe(true); 24 | expect(prefixes.includes('datetime')).toBe(true); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/src/common/parse/cypherKeywords.js: -------------------------------------------------------------------------------- 1 | 2 | const keywords = ` 3 | WITH 4 | UNWIND 5 | CREATE 6 | DELETE 7 | DETACH 8 | LIMIT 9 | MATCH 10 | MERGE 11 | OPTIONAL MATCH 12 | ORDER BY 13 | REMOVE 14 | RETURN 15 | SET 16 | SKIP 17 | WHERE 18 | WITH 19 | UNION 20 | UNWIND 21 | `; 22 | 23 | export const CypherKeywords = keywords.split('\n').map(x => x.trim()).filter(x => x); -------------------------------------------------------------------------------- /ui/src/common/parse/parseUtil.js: -------------------------------------------------------------------------------- 1 | 2 | export const hasDescendentsOfType = (parseNode, type) => { 3 | if (parseNode && parseNode.children) { 4 | const match = parseNode.children.find(child => { 5 | const childType = getType(child); 6 | //console.log(`checking childType ${childType} vs ${type}`); 7 | return childType === type || hasDescendentsOfType(child, type); 8 | }); 9 | return (match) ? true : false; 10 | } 11 | return false; 12 | } 13 | 14 | export const getDescendentsOfType = (parseNode, type) => { 15 | var descendents = []; 16 | if (parseNode && parseNode.children) { 17 | parseNode.children.map(child => { 18 | if (getType(child) === type) { 19 | descendents.push(child); 20 | } 21 | descendents = descendents.concat(getDescendentsOfType(child, type)); 22 | }); 23 | } 24 | return descendents; 25 | } 26 | 27 | export const getType = (parseNode) => (typeof(parseNode) === 'object') ? parseNode.constructor.name : null; 28 | 29 | export const processTokens = (tokens, parser, cypherLines, options) => { 30 | options = options || {}; 31 | var returnTokens = []; 32 | if (tokens) { 33 | returnTokens = tokens 34 | .map(token => { 35 | const lineIndex = token.line - 1; 36 | const line = (cypherLines) ? cypherLines[lineIndex] : ''; 37 | return { 38 | type: parser.symbolicNames[token.type], 39 | text: line.substring(token.start, token.stop + 1) 40 | } 41 | }) 42 | 43 | if (!options.leaveSpaces) { 44 | returnTokens = returnTokens.filter(textWithType => textWithType.type !== 'SP'); 45 | } 46 | } 47 | return returnTokens; 48 | } -------------------------------------------------------------------------------- /ui/src/common/parse/unitTest.test.js: -------------------------------------------------------------------------------- 1 | const { parseTestCypher, renderTestCypher } = require('./unitTest'); 2 | 3 | test('test parseTestCypher', () => { 4 | var testCypher = ` 5 | // Foo exists 6 | MATCH (n:Foo) 7 | RETURN n 8 | /* 9 | expect exists(n) = true 10 | */; 11 | ` 12 | 13 | var result = parseTestCypher(testCypher); 14 | expect(result.name).toBe('Foo exists'); 15 | expect(result.cypher).toBe('MATCH (n:Foo)\nRETURN n'); 16 | expect(result.returnVars.length).toBe(1); 17 | expect(result.returnVars[0]).toBe('n'); 18 | expect(result.assertions.length).toBe(1); 19 | expect(result.assertions[0]).toEqual({ 20 | assertion: 'exists(n) = true', 21 | left: 'exists(n)', 22 | condition: '=', 23 | right: 'true' 24 | }); 25 | }); 26 | 27 | test('test renderTestCypher', () => { 28 | var testCypher = ` 29 | // Foo exists 30 | MATCH (n:Foo) 31 | RETURN n 32 | /* 33 | expect exists(n) = true 34 | */; 35 | ` 36 | var result = parseTestCypher(testCypher); 37 | var renderedCypher = renderTestCypher(result); 38 | //console.log(renderedCypher); 39 | }); 40 | -------------------------------------------------------------------------------- /ui/src/common/util/download.js: -------------------------------------------------------------------------------- 1 | 2 | export function downloadUrlAsFile (url, fileName) { 3 | var downloadLink = document.createElement("a"); 4 | downloadLink.href = url; 5 | downloadLink.download = fileName; 6 | document.body.appendChild(downloadLink); 7 | downloadLink.click(); 8 | document.body.removeChild(downloadLink); 9 | } 10 | -------------------------------------------------------------------------------- /ui/src/common/util/timeUtil.test.js: -------------------------------------------------------------------------------- 1 | 2 | import Timer from './timerUtil'; 3 | 4 | 5 | test('test timer util', async () => { 6 | var timer = new Timer('testTimer'); 7 | timer.setSilent(true); 8 | timer.start('starting'); 9 | timer.record('event 1'); 10 | 11 | await new Promise(resolve => setTimeout(resolve, 20)); 12 | 13 | timer.record('event 2'); 14 | timer.stop('stopping'); 15 | 16 | var timedMessages = timer.getTimedMessages(); 17 | expect(timedMessages.length).toBe(4); 18 | expect(timedMessages[0].deltaTime).toBe(0); 19 | expect(timedMessages[1].deltaTime).toBeLessThanOrEqual(2); 20 | expect(timedMessages[2].deltaTime).toBeGreaterThanOrEqual(18); 21 | expect(timedMessages[3].deltaTime).toBeGreaterThanOrEqual(0); 22 | }); 23 | 24 | -------------------------------------------------------------------------------- /ui/src/common/util/tracking.js: -------------------------------------------------------------------------------- 1 | 2 | import packageInfo from "../../../package.json"; 3 | import { getDynamicConfigValue, getAppTrackingName } from "../../dynamicConfig"; 4 | import { getAuth } from "../../auth/authUtil"; 5 | 6 | const trackingEnabled = (getDynamicConfigValue('REACT_APP_SEGMENT_API_KEY')) ? true : false; 7 | 8 | export const track = (eventName, eventParams) => { 9 | if (trackingEnabled) { 10 | eventParams = eventParams || {}; 11 | eventParams = { 12 | ...eventParams, 13 | appName: getAppTrackingName(), 14 | version: packageInfo.version, 15 | ...getAuth().getIdentityInfo() 16 | } 17 | window.analytics.track(`${eventName}`, eventParams); 18 | } 19 | } 20 | 21 | export const identify = () => { 22 | if (trackingEnabled) { 23 | var identityInfo = getAuth().getIdentityInfo(); 24 | 25 | var eventParams = { 26 | appName: getAppTrackingName(), 27 | version: packageInfo.version, 28 | ...identityInfo 29 | } 30 | window.analytics.identify(identityInfo.email, eventParams); 31 | } 32 | } -------------------------------------------------------------------------------- /ui/src/components/canvas/CanvasDataProviderInterface.js: -------------------------------------------------------------------------------- 1 | 2 | export class CanvasDataProviderInterface { 3 | constructor () {} 4 | 5 | // each node should have a key property 6 | getNodes () {} 7 | getNodeByKey (key) {} 8 | 9 | // each relationship should have a key property 10 | getRelationships () {} 11 | getRelationshipByKey (key) {} 12 | 13 | // called when the canvas needs to add a new node 14 | getNewNode () {} 15 | 16 | // called when the canvas needs to add a new relationship / node combination 17 | getNewRelNode () {} 18 | } -------------------------------------------------------------------------------- /ui/src/components/canvas/GraphCanvasRole.js: -------------------------------------------------------------------------------- 1 | 2 | import { USER_ROLE, ALERT_TYPES } from '../../common/Constants'; 3 | 4 | export class GraphCanvasRole { 5 | 6 | constructor () { 7 | this.role = null; 8 | } 9 | 10 | setRole = (role) => { 11 | this.role = role; 12 | } 13 | 14 | getRole = () => { 15 | return this.role; 16 | } 17 | 18 | getRoleDisplay = (role) => { 19 | role = (role) ? role : this.role; 20 | if (role === USER_ROLE.MEMBER) { 21 | return 'EDITOR'; 22 | } else { 23 | return (role) ? role : 'Unknown'; 24 | } 25 | } 26 | 27 | checkRole = (allowedRoles, showMessage) => { 28 | // if a single role is passed in, convert it to an array 29 | allowedRoles = (typeof(allowedRoles) === 'string') ? [allowedRoles] : allowedRoles; 30 | if (allowedRoles.includes(this.role)) { 31 | return true; 32 | } else { 33 | if (showMessage) { 34 | alert('Insufficient permission, your role is ' + this.getRoleDisplay(), ALERT_TYPES.WARNING); 35 | } 36 | return false; 37 | } 38 | } 39 | 40 | canEdit = (showMessage) => { 41 | return this.checkRole([USER_ROLE.OWNER, USER_ROLE.MEMBER], showMessage); 42 | } 43 | 44 | isOwner = (showMessage) => { 45 | return this.checkRole([USER_ROLE.OWNER], showMessage); 46 | } 47 | 48 | canEditShowMessage = () => { 49 | return this.checkRole([USER_ROLE.OWNER, USER_ROLE.MEMBER], true); 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /ui/src/components/canvas/ZoomControls.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function ZoomControls (props) { 4 | var { style, zoomIn, zoomOut, resetPanAndZoom, zoomLevel } = props; 5 | var radius = 30; 6 | 7 | return ( 8 |
9 |
Zoom: {zoomLevel}%
10 |
zoomOut()} title="Zoom Out" 11 | style={{ height: radius + 'px', width: radius + 'px', 12 | display: 'inline-block', margin: '3px', 13 | backgroundColor: '#DDD', borderRadius: '50%', cursor: 'pointer'}}> 14 | 15 |
16 |
zoomIn()} title="Zoom In" 17 | style={{ height: radius + 'px', width: radius + 'px', 18 | display: 'inline-block', margin: '3px', verticalAlign: 'middle', 19 | backgroundColor: '#DDD', borderRadius: '50%', cursor: 'pointer'}}> 20 | 21 |
22 |
resetPanAndZoom()} title="Reset" 23 | style={{ height: radius + 'px', width: radius + 'px', 24 | display: 'inline-block', margin: '3px', 25 | backgroundColor: '#DDD', borderRadius: '50%', cursor: 'pointer'}}> 26 | 27 |
28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /ui/src/components/canvas/d3/canvasConfigDefaultConstants.js: -------------------------------------------------------------------------------- 1 | 2 | export var DEFAULT_CONSTANTS = { 3 | 4 | DEFAULT_FONT_SIZE: 14, 5 | 6 | DEFAULT_RELATIONSHIP_DISPLAY: "fill-me-in", 7 | 8 | MARKER_WIDTH: 4, 9 | MARKER_HEIGHT: 4, 10 | LINE_GAP: 5, 11 | 12 | MAX_ZOOM_IN: 2.0, 13 | MAX_ZOOM_OUT: 0.2, 14 | 15 | REL_RIBBON_BUTTON_WIDTH: 24, 16 | REL_RIBBON_BUTTON_HEIGHT: 24, 17 | 18 | HOVER_CIRCLE_RADIUS_INCREASE: 20, 19 | 20 | NODE_ANNOTATION_OFFSET: 5, 21 | ANNOTATION_X_OFFSET_DEFAULT: 2, // for relationships 22 | ANNOTATION_Y_OFFSET_DEFAULT: 2 // for relationships 23 | } 24 | 25 | -------------------------------------------------------------------------------- /ui/src/components/common/FullScreenWaitOverlay.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Backdrop from '@material-ui/core/Backdrop'; 3 | import CircularProgress from '@material-ui/core/CircularProgress'; 4 | import { makeStyles } from '@material-ui/core/styles'; 5 | 6 | const useStyles = makeStyles((theme) => ({ 7 | backdrop: { 8 | zIndex: theme.zIndex.drawer + 1, 9 | color: '#fff', 10 | }, 11 | })); 12 | 13 | export default function FullScreenWaitOverlay ({open, setOpen}) { 14 | 15 | const classes = useStyles(); 16 | 17 | const handleClose = () => { 18 | //setOpen(false); 19 | } 20 | 21 | return ( 22 | 23 | 24 | 25 | ) 26 | } -------------------------------------------------------------------------------- /ui/src/components/common/GeneralDialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Button, Dialog, DialogActions, 4 | DialogContent, DialogContentText, DialogTitle 5 | } from '@material-ui/core'; 6 | import { StyledButton } from './Components'; 7 | 8 | export default function GeneralDialog(props) { 9 | 10 | const closeFunc = props.onClose || props.handleClose; 11 | 12 | return ( 13 | 19 | {props.title} 20 | 21 | {(props.isReactEl) ? 22 | props.description 23 | : 24 | 25 | {props.description} 26 | 27 | } 28 | 29 | 30 | {props.buttons.map((button, index) => { 31 | return ( 32 | button.onClick(button, index, closeFunc)} color="primary" autoFocus={button.autofocus}> 33 | {button.text} 34 | 35 | ) 36 | })} 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /ui/src/components/menu/CheckmarkMenuItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { 3 | MenuItem 4 | } from '@material-ui/core'; 5 | 6 | export default class CheckmarkMenuItem extends Component { 7 | 8 | constructor (props) { 9 | super(props); 10 | } 11 | 12 | render() { 13 | var { menuId, menuItem } = this.props; 14 | //console.log('checkmark menu item: ', menuItem); 15 | return ( 16 | 18 | {menuItem.text} 19 | {(menuItem.checked) && } 20 | 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ui/src/components/menu/EnhancedMenu.js: -------------------------------------------------------------------------------- 1 | // from https://github.com/mui-org/material-ui/issues/11723 2 | import React from "react"; 3 | import PropTypes from "prop-types"; 4 | import MuiMenu from "@material-ui/core/Menu"; 5 | import MenuItem from "@material-ui/core/MenuItem"; 6 | import SubMenu from "./SubMenu"; 7 | import CheckmarkMenuItem from "./CheckmarkMenuItem"; 8 | 9 | export default class EnhancedMenu extends React.Component { 10 | renderMenuItems = () => { 11 | const { menuItems, className } = this.props; 12 | return menuItems.map(menuItem => { 13 | return ( 14 | 15 | /* 16 | 17 | {menuItem.text} 18 | 19 | */ 20 | ); 21 | }); 22 | }; 23 | 24 | render() { 25 | const { anchorElement, open, onClose, ...others } = this.props; 26 | const { menuItems, ...restOfProps } = others; 27 | return ( 28 | 34 | {this.renderMenuItems()} 35 | 36 | ); 37 | } 38 | } 39 | 40 | EnhancedMenu.propTypes = { 41 | anchorElement: PropTypes.any, 42 | menuItems: PropTypes.array.isRequired, 43 | onClose: PropTypes.func.isRequired, 44 | open: PropTypes.bool.isRequired 45 | }; 46 | -------------------------------------------------------------------------------- /ui/src/components/menu/StatefulCheckmarkMenuItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { 3 | MenuItem 4 | } from '@material-ui/core'; 5 | 6 | export default class StatefulCheckmarkMenuItem extends Component { 7 | 8 | state = { 9 | checked: false 10 | } 11 | 12 | constructor (props) { 13 | super(props); 14 | var { menuItem } = this.props; 15 | menuItem = menuItem || {}; 16 | var { checked } = menuItem; 17 | this.state.checked = checked; 18 | 19 | // this is an unfortunate hack...this menu item may be rendered before we know 20 | // whether it should be checked or not, and since it may be loaded from a deeply 21 | // nested state tree, it would be difficult for a ref to be passed around 22 | 23 | // therefore, when this component loads, we will pass a reference to the setChecked() 24 | // function as an argument to the loadedCallback 25 | 26 | var { menuItem } = this.props; 27 | menuItem = menuItem || {}; 28 | var { onLoaded } = menuItem; 29 | 30 | if (onLoaded) { 31 | onLoaded({setChecked: this.setChecked}); 32 | } 33 | } 34 | 35 | setChecked = (checked) => { 36 | this.setState({ 37 | checked: checked 38 | }); 39 | } 40 | 41 | render() { 42 | var { checked } = this.state; 43 | var { menuId, menuItem } = this.props; 44 | //console.log('checkmark menu item: ', menuItem); 45 | return ( 46 | menuItem.onClick({ checked: checked, setChecked: this.setChecked })} 47 | style={{ alignItems: 'baseline', fontSize: '1em', minWidth: '8em', minHeight: '0px' }}> 48 | {menuItem.text} 49 | {(checked) && } 50 | 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ui/src/components/menu/SubMenu.js: -------------------------------------------------------------------------------- 1 | // from https://github.com/mui-org/material-ui/issues/11723 2 | import React from "react"; 3 | import PropTypes from "prop-types"; 4 | import MenuItem from "@material-ui/core/MenuItem"; 5 | import ArrowRightIcon from "@material-ui/icons/KeyboardArrowRight"; 6 | import withStyles from "@material-ui/core/styles/withStyles"; 7 | import EnhancedMenu from "./EnhancedMenu"; 8 | 9 | const styles = { 10 | subMenuItem: { 11 | display: "flex", 12 | justifyContent: "space-between" 13 | } 14 | }; 15 | 16 | class SubMenu extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | 20 | this.state = { 21 | menuOpen: false, 22 | anchorElement: null 23 | }; 24 | } 25 | 26 | handleItemClick = event => { 27 | if (!this.anchorElement) { 28 | this.setState({ 29 | anchorElement: event.currentTarget 30 | }); 31 | } 32 | 33 | this.setState({ 34 | menuOpen: !this.menuOpen 35 | }); 36 | }; 37 | 38 | handleSubMenuClose = () => { 39 | this.setState({ 40 | menuOpen: false 41 | }); 42 | }; 43 | 44 | render() { 45 | const { text, menuItems, className } = this.props; 46 | const { anchorElement, menuOpen } = this.state; 47 | return ( 48 | 49 | 54 | {text} 55 | 56 | 57 | 72 | 73 | ); 74 | } 75 | } 76 | 77 | SubMenu.propTypes = { 78 | text: PropTypes.string.isRequired, 79 | classes: PropTypes.any, 80 | menuItems: PropTypes.array.isRequired 81 | }; 82 | 83 | export default withStyles(styles)(SubMenu); 84 | -------------------------------------------------------------------------------- /ui/src/dataModel/DataTypes.js: -------------------------------------------------------------------------------- 1 | const DataTypes = { 2 | String: "String", 3 | Integer: "Integer", 4 | Float: "Float", 5 | Boolean: "Boolean", 6 | Date: "Date", 7 | Time: "Time", 8 | LocalTime: "LocalTime", 9 | DateTime: "DateTime", 10 | LocalDateTime: "LocalDateTime", 11 | Duration: "Duration", 12 | Point: "Point", 13 | StringArray: "String Array", 14 | IntegerArray: "Integer Array", 15 | FloatArray: "Float Array", 16 | BooleanArray: "Boolean Array", 17 | DateArray: "Date Array", 18 | TimeArray: "Time Array", 19 | LocalTimeArray: "LocalTime Array", 20 | DateTimeArray: "DateTime Array", 21 | LocalDateTimeArray: "LocalDateTime Array", 22 | DurationArray: "Duration Array", 23 | PointArray: "Point Array" 24 | }; 25 | 26 | export default DataTypes; 27 | 28 | -------------------------------------------------------------------------------- /ui/src/dataModel/JaroWrinker.js: -------------------------------------------------------------------------------- 1 | 2 | // taken from https://medium.com/@sumn2u/string-similarity-comparision-in-js-with-examples-4bae35f13968 3 | export default function JaroWrinker (s1, s2) { 4 | var m = 0, i, j, n_trans; 5 | 6 | // Exit early if either are empty. 7 | if ( s1.length === 0 || s2.length === 0 ) { 8 | return 0; 9 | } 10 | 11 | // Exit early if they're an exact match. 12 | if ( s1 === s2 ) { 13 | return 1; 14 | } 15 | 16 | var range = (Math.floor(Math.max(s1.length, s2.length) / 2)) - 1, 17 | s1Matches = new Array(s1.length), 18 | s2Matches = new Array(s2.length); 19 | 20 | for ( i = 0; i < s1.length; i++ ) { 21 | var low = (i >= range) ? i - range : 0, 22 | high = (i + range <= s2.length) ? (i + range) : (s2.length - 1); 23 | 24 | for ( j = low; j <= high; j++ ) { 25 | if ( s1Matches[i] !== true && s2Matches[j] !== true && s1[i] === s2[j] ) { 26 | ++m; 27 | s1Matches[i] = s2Matches[j] = true; 28 | break; 29 | } 30 | } 31 | } 32 | 33 | // Exit early if no matches were found. 34 | if ( m === 0 ) { 35 | return 0; 36 | } 37 | 38 | // Count the transpositions. 39 | var k = n_trans = 0; 40 | 41 | for ( i = 0; i < s1.length; i++ ) { 42 | if ( s1Matches[i] === true ) { 43 | for ( j = k; j < s2.length; j++ ) { 44 | if ( s2Matches[j] === true ) { 45 | k = j + 1; 46 | break; 47 | } 48 | } 49 | 50 | if ( s1[i] !== s2[j] ) { 51 | ++n_trans; 52 | } 53 | } 54 | } 55 | 56 | var weight = (m / s1.length + m / s2.length + (m - (n_trans / 2)) / m) / 3, 57 | l = 0, 58 | p = 0.1; 59 | 60 | if ( weight > 0.7 ) { 61 | while ( s1[l] === s2[l] && l < 4 ) { 62 | ++l; 63 | } 64 | 65 | weight = weight + l * p * (1 - weight); 66 | } 67 | 68 | return weight; 69 | } 70 | -------------------------------------------------------------------------------- /ui/src/dataModel/cypherClause.js: -------------------------------------------------------------------------------- 1 | 2 | import SnippetSet from "./cypherSnippetSet"; 3 | 4 | export default class CypherClause { 5 | 6 | constructor (properties) { 7 | properties = properties || {}; 8 | 9 | var { 10 | keyword, 11 | clauseInfo, 12 | parsedVariables 13 | } = properties; 14 | 15 | this.keyword = keyword; 16 | this.clauseInfo = clauseInfo; 17 | this.parsedVariables = parsedVariables || []; 18 | 19 | // to help generate the proper RETURN a, b when generating debugging snippets 20 | this.debugSubQueryReturnHandler = null; 21 | } 22 | 23 | toCypherString = () => { 24 | let clauseStr = (this.clauseInfo && this.clauseInfo.toCypherString) ? this.clauseInfo.toCypherString() : this.clauseInfo; 25 | // avoid double adding the keyword 26 | let keywordStr = `${this.keyword} ` 27 | if (typeof(clauseStr) === 'string') { 28 | let clauseCheck = clauseStr.trim().toLowerCase(); 29 | let keywordCheck = keywordStr.toLowerCase() 30 | if (clauseCheck.startsWith(keywordCheck)) { 31 | keywordStr = ''; 32 | } 33 | } 34 | return `${keywordStr}${clauseStr}`; 35 | } 36 | 37 | getDebugCypherSnippetSet = (config = {}) => { 38 | // console.log('config: ', config); 39 | let { addNewLineIfCypherClause } = config; 40 | let optionalNewLine = (addNewLineIfCypherClause) ? '\n' : ''; 41 | let snippetSet = new SnippetSet({ 42 | opener: `${optionalNewLine}${this.keyword} `, 43 | associatedCypherObject: this, 44 | subQueryReturn: this.debugSubQueryReturnHandler 45 | }); 46 | 47 | if (this.clauseInfo) { 48 | if (this.clauseInfo.getDebugCypherSnippetSet) { 49 | snippetSet.addOneOrMoreSnippets(this.clauseInfo.getDebugCypherSnippetSet(config)); 50 | } else { 51 | snippetSet.addOneOrMoreSnippets(this.clauseInfo); 52 | } 53 | } 54 | 55 | snippetSet.cypherSnippet = snippetSet.computeCypherSnippet(); 56 | return snippetSet; 57 | } 58 | 59 | getDebugCypherSnippets = (config) => { 60 | let snippetSet = this.getDebugCypherSnippetSet(config); 61 | let snippets = snippetSet.getSnippets(); 62 | return snippets; 63 | } 64 | } -------------------------------------------------------------------------------- /ui/src/dataModel/cypherClause.test.js: -------------------------------------------------------------------------------- 1 | 2 | import CypherClause from "./cypherClause"; 3 | 4 | test('make cypher clause', () => { 5 | var cypherClause = new CypherClause(); 6 | expect(cypherClause).not.toBeNull(); 7 | }); 8 | 9 | test('cypher clause toCypherString', () => { 10 | var cypherClause = new CypherClause({ 11 | keyword: 'WITH', 12 | clauseInfo: '1 as number' 13 | }); 14 | 15 | expect(cypherClause.toCypherString()).toBe('WITH 1 as number'); 16 | }); 17 | 18 | test('cypher clause associatedCypherObject', () => { 19 | var cypherClause = new CypherClause({ 20 | keyword: 'WITH', 21 | clauseInfo: '1 as number' 22 | }); 23 | 24 | let snippetSet = cypherClause.getDebugCypherSnippetSet(); 25 | // console.log('snippetSet: ', snippetSet) 26 | expect(snippetSet.associatedCypherObject).toBe(cypherClause); 27 | }); -------------------------------------------------------------------------------- /ui/src/dataModel/cypherLimit.js: -------------------------------------------------------------------------------- 1 | 2 | export const LIMIT_CLAUSE_DEFAULT_LIMIT = ''; 3 | 4 | export class LimitClause { 5 | constructor (properties) { 6 | properties = properties || {}; 7 | var { limit } = properties; 8 | 9 | this.limit = (limit !== undefined) ? limit : LIMIT_CLAUSE_DEFAULT_LIMIT; 10 | } 11 | 12 | getLimit = () => this.limit; 13 | setLimit = (limit) => { 14 | this.limit = limit; 15 | return this; 16 | } 17 | 18 | toCypherString = () => `LIMIT ${this.limit}`; 19 | } -------------------------------------------------------------------------------- /ui/src/dataModel/cypherSkip.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const SKIP_CLAUSE_DEFAULT_SKIP = ''; 4 | 5 | export class SkipClause { 6 | constructor (properties) { 7 | properties = properties || {}; 8 | var { skip } = properties; 9 | 10 | this.skip = (skip !== undefined) ? skip : SKIP_CLAUSE_DEFAULT_SKIP; 11 | } 12 | 13 | getSkip = () => this.skip; 14 | setSkip = (skip) => { 15 | this.skip = skip; 16 | return this; 17 | } 18 | 19 | toCypherString = () => `SKIP ${this.skip}`; 20 | } -------------------------------------------------------------------------------- /ui/src/dataModel/cypherStatementBuilder.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | NodePattern, 4 | RelationshipPattern, 5 | PathPattern, 6 | PatternPart, 7 | PatternElementChainLink, 8 | Pattern, 9 | QuantifiedPathPattern 10 | } from './cypherPattern'; 11 | import { WhereClause } from './cypherWhere'; 12 | 13 | export class CypherStatementBuilder { 14 | 15 | constructor (properties) { 16 | properties = (properties) ? properties : {}; 17 | var { 18 | variableScope 19 | } = properties; 20 | 21 | this.variableScope = variableScope; 22 | } 23 | 24 | setVariableScope = (variableScope) => this.variableScope = variableScope; 25 | 26 | node = () => new NodePattern({ variableScope: this.variableScope }); 27 | rel = () => new RelationshipPattern({ variableScope: this.variableScope }); 28 | path = () => new PathPattern({ variableScope: this.variableScope }); 29 | link = () => new PatternElementChainLink({ variableScope: this.variableScope }); 30 | part = () => new PatternPart({ variableScope: this.variableScope }); 31 | pattern = () => new Pattern({ variableScope: this.variableScope }); 32 | qpp = () => new QuantifiedPathPattern({ variableScope: this.variableScope }); 33 | where = () => new WhereClause(); 34 | } -------------------------------------------------------------------------------- /ui/src/dataModel/dataModelExtension.basic.js: -------------------------------------------------------------------------------- 1 | 2 | import { getBasicLicenseMessage } from '../common/LicensedFeatures'; 3 | 4 | export function getConstraintStatementsEx (nodeLabels, relationshipTypes) { 5 | return getBasicLicenseMessage(); 6 | } 7 | -------------------------------------------------------------------------------- /ui/src/dataModel/dataSource/cypherDataInstructions.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export class MergeInstruction { 4 | constructor (properties) { 5 | properties = properties || {}; 6 | var { 7 | patternPart, 8 | onMatchSetItems, 9 | onCreateSetItems 10 | } = properties; 11 | this.patternPart = patternPart; 12 | this.onMatchSetItems = onMatchSetItems; 13 | this.onCreateSetItems = onCreateSetItems; 14 | } 15 | 16 | toCypher = () => { 17 | var cypher = `MERGE ${this.patternPart.toCypherString()}`; 18 | if (this.onCreateSetItems) { 19 | cypher += `\n ON CREATE ${this.onCreateSetItems.toCypher()}`; 20 | } 21 | if (this.onMatchSetItems) { 22 | cypher += `\n ON MATCH ${this.onMatchSetItems.toCypher()}`; 23 | } 24 | return cypher; 25 | } 26 | } 27 | 28 | export class SetItem { 29 | constructor (properties) { 30 | properties = properties || {}; 31 | var { 32 | leftExpr, 33 | operator, 34 | rightExpr 35 | } = properties; 36 | this.leftExpr = leftExpr; 37 | this.operator = operator; 38 | this.rightExpr = rightExpr; 39 | } 40 | 41 | toCypher = () => (this.operator) 42 | ? `${this.leftExpr} ${this.operator} ${this.rightExpr}` 43 | : `${this.leftExpr}${this.rightExpr}` // for setting NodeLabels, e.g. SET person:Person:Actor 44 | } 45 | 46 | export class SetItems { 47 | constructor (properties) { 48 | properties = properties || {}; 49 | var { setItems } = properties; 50 | this.setItems = setItems || []; 51 | } 52 | 53 | toCypher = () => 'SET ' + this.setItems.map(x => x.toCypher()).join(', '); 54 | } -------------------------------------------------------------------------------- /ui/src/dataModel/dataSource/dataMapping.js: -------------------------------------------------------------------------------- 1 | 2 | export class Source { 3 | constructor (properties) { 4 | properties = properties || {}; 5 | } 6 | } 7 | 8 | export class ColumnSource extends Source { 9 | constructor (properties) { 10 | super(properties); 11 | properties = properties || {}; 12 | var { 13 | key, 14 | tableData, 15 | tableCatalog, 16 | tableDefinition, 17 | columnDefinition, 18 | columnIndex 19 | } = properties; 20 | 21 | this.key = key; 22 | this.tableData = tableData; 23 | this.tableCatalog = tableCatalog; 24 | this.tableDefinition = tableDefinition; 25 | this.columnDefinition = columnDefinition; 26 | this.columnIndex = columnIndex; 27 | } 28 | } 29 | 30 | export class Destination { 31 | constructor (properties) { 32 | properties = properties || {}; 33 | } 34 | } 35 | 36 | export class GraphDestination extends Destination { 37 | constructor (properties) { 38 | super(properties); 39 | properties = properties || {}; 40 | var { 41 | key, 42 | dataModel, 43 | // either nodePattern / nodeLabel will be populated 44 | nodePattern, 45 | nodeLabel, 46 | // -or- nodeRelNodePattern / relationshipType will be populated 47 | nodeRelNodePattern, 48 | relationshipType, 49 | propertyDefinition 50 | } = properties; 51 | 52 | this.key = key; 53 | this.dataModel = dataModel; 54 | this.nodePattern = nodePattern; 55 | this.nodeLabel = nodeLabel; 56 | this.nodeRelNodePattern = nodeRelNodePattern; 57 | this.relationshipType = relationshipType; 58 | this.propertyDefinition = propertyDefinition; 59 | } 60 | } 61 | 62 | export class DataMapping { 63 | constructor (properties) { 64 | properties = properties || {}; 65 | var { 66 | key, 67 | source, 68 | destination 69 | } = properties; 70 | 71 | this.key = key; 72 | this.source = source; 73 | this.destination = destination; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ui/src/dataModel/dataSource/generalDataSource.test.js: -------------------------------------------------------------------------------- 1 | 2 | import { deserializeObject, serializeObject } from '../graphUtil'; 3 | import DataTypes from '../DataTypes'; 4 | import { GeneralDataSourceInstanceMapper } from './generalDataSource'; 5 | import { 6 | createSampleDataDefinition, 7 | } from './testHelper'; 8 | 9 | test('test serialize/deserialize data definition', () => { 10 | 11 | expect(true).toBe(true); 12 | var dataDef = createSampleDataDefinition(); 13 | 14 | var json = serializeObject(dataDef); 15 | 16 | //console.log(json); 17 | var deserializedDataDef = deserializeObject(json, GeneralDataSourceInstanceMapper); 18 | //console.log(deserializedDataDef); 19 | 20 | var tableDef = deserializedDataDef.getTableDefinitionByKey('table1'); 21 | 22 | expect(tableDef).not.toBeNull(); 23 | 24 | var companyDef = tableDef.getColumnDefinitionByName('company'); 25 | expect(companyDef).not.toBeNull(); 26 | 27 | expect(companyDef.datatype).toBe(DataTypes.String); 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /ui/src/dataModel/dataSource/tableData.js: -------------------------------------------------------------------------------- 1 | 2 | export class TableData { 3 | constructor (properties) { 4 | properties = properties || {}; 5 | 6 | var { 7 | rows, 8 | dataSource, 9 | tableCatalog, 10 | tableDefinition, 11 | columnDefinitions 12 | } = properties; 13 | 14 | this.rows = rows || []; 15 | this.dataSource = dataSource; 16 | this.tableCatalog = tableCatalog; 17 | this.tableDefinition = tableDefinition; 18 | this.columnDefinitions = columnDefinitions; 19 | } 20 | 21 | addRow = (columns) => { 22 | this.rows.push(columns); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /ui/src/dataModel/dataSource/transform.test.js: -------------------------------------------------------------------------------- 1 | 2 | import DataTypes from '../DataTypes'; 3 | import { 4 | createSampleData, 5 | createSampleDataDefinition, 6 | createSampleDataModel 7 | } from './testHelper'; 8 | 9 | test('make sample data', () => { 10 | var sampleData = createSampleData(); 11 | expect(sampleData.rows.length).toBe(2); 12 | 13 | var colIndex = 5; 14 | expect(sampleData.rows[1][colIndex]).toBe('Chipotle'); 15 | var tableDef = sampleData.tableDefinition; 16 | var companyDef = tableDef.columnDefinitions[colIndex]; 17 | 18 | expect(companyDef).not.toBeNull(); 19 | expect(companyDef.datatype).toBe(DataTypes.String); 20 | }); 21 | 22 | test('make table definition', () => { 23 | var dataDef = createSampleDataDefinition(); 24 | var tableDef = dataDef.getTableDefinitionByKey('table1'); 25 | 26 | expect(tableDef).not.toBeNull(); 27 | 28 | var companyDef = tableDef.getColumnDefinitionByName('company'); 29 | expect(companyDef).not.toBeNull(); 30 | 31 | expect(companyDef.datatype).toBe(DataTypes.String); 32 | }); 33 | 34 | test('make sample data model', () => { 35 | var dataModel = createSampleDataModel(); 36 | 37 | var relationshipTypes = dataModel.getRelationshipTypeArray(); 38 | expect(relationshipTypes.length).toBe(2); 39 | 40 | expect(relationshipTypes[0].type).toBe('LIVES_AT'); 41 | expect(relationshipTypes[0].startNodeLabel.label).toBe('Person'); 42 | expect(relationshipTypes[0].endNodeLabel.label).toBe('Address'); 43 | 44 | expect(relationshipTypes[1].type).toBe('WORKS_FOR'); 45 | expect(relationshipTypes[1].startNodeLabel.label).toBe('Person'); 46 | expect(relationshipTypes[1].endNodeLabel.label).toBe('Company'); 47 | }); 48 | 49 | -------------------------------------------------------------------------------- /ui/src/dataModel/dataSource/transformationStep.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export class TransformationStep { 4 | constructor (properties) { 5 | properties = properties || {}; 6 | var { transformations } = properties; 7 | this.transformations = transformations; 8 | } 9 | 10 | toCypher = () => { 11 | var cypher = 'WITH '; 12 | cypher += this.transformations.map(x => { 13 | const cypher = x.toCypher(); 14 | return cypher; 15 | }).join(', '); 16 | return cypher; 17 | } 18 | } -------------------------------------------------------------------------------- /ui/src/dataModel/dataValidation.js: -------------------------------------------------------------------------------- 1 | 2 | export const ERROR_MESSAGES = { 3 | NODE_LABEL_TEXT_CANNOT_BE_EMPTY: "Node Label text cannot not be empty.", 4 | ITEM_CANNOT_CONTAIN_COLON: "Item cannot contain a colon.", 5 | NODE_LABEL_MUST_BE_UNIQUE: "Node Labels must be unique. That label is already taken.", 6 | SECONDARY_NODE_LABEL_MUST_BE_UNIQUE: "Used by a secondary node label on $nodeLabel. To add it, use Model Input.", 7 | RELATIONSHIP_LABEL_CANNOT_HAVE_PIPE: "Relationship Type text cannot contain the '|' symbol." 8 | } 9 | 10 | export function validateText (propertyContainer, dataModel, newText) { 11 | if (propertyContainer.classType == "NodeLabel") { 12 | if (!newText) { 13 | alert(ERROR_MESSAGES.NODE_LABEL_TEXT_CANNOT_BE_EMPTY); 14 | return false; 15 | } else { 16 | /* 17 | var labelObj = dataModel.processLabel(newText); 18 | var existingNodeLabel = dataModel.getNodeLabelByLabel(labelObj.primaryNodeLabelString); 19 | if (existingNodeLabel) { 20 | if (existingNodeLabel.isOnlySecondaryNodeLabel) { 21 | var usedBy = dataModel.getNodeLabelArray().find(x => 22 | x.secondaryNodeLabelKeys.includes(existingNodeLabel.key)); 23 | 24 | var message = ERROR_MESSAGES.SECONDARY_NODE_LABEL_MUST_BE_UNIQUE; 25 | var nodeLabelText = (usedBy) ? usedBy.getTextWithSecondaryNodeLabels() : 'Unknown' 26 | message = message.replace(/\$nodeLabel/, nodeLabelText); 27 | alert(message); 28 | return false; 29 | } else { 30 | alert(ERROR_MESSAGES.NODE_LABEL_MUST_BE_UNIQUE); 31 | return false; 32 | } 33 | } 34 | */ 35 | } 36 | } else if (propertyContainer.classType == "RelationshipType" && newText.match(/\|/)) { 37 | alert(ERROR_MESSAGES.RELATIONSHIP_LABEL_CANNOT_HAVE_PIPE) 38 | return false; 39 | } 40 | return true; 41 | } -------------------------------------------------------------------------------- /ui/src/dataModel/debugVariableUtil.js: -------------------------------------------------------------------------------- 1 | 2 | import { VariableContainer } from "./cypherPattern"; 3 | import { findObjectsOfACertainClass } from "./graphUtil"; 4 | 5 | export const getVariables = (allClauses) => { 6 | // console.log('all clauses: ', allClauses) 7 | let variableContainers = findObjectsOfACertainClass(allClauses, VariableContainer); 8 | // console.log('variableContainers: ', variableContainers) 9 | let variables = variableContainers 10 | .map(container => container.variable) 11 | .filter(variable => variable) // remove empty variables 12 | variables = [...new Set(variables)]; 13 | return variables; 14 | 15 | // Things to think about: 16 | // for non-Pattern, include parsedVariables? 17 | // otherwise - explicity look at UNWIND or ReturnBody? 18 | 19 | } 20 | -------------------------------------------------------------------------------- /ui/src/dataModel/debugVariableUtil.test.js: -------------------------------------------------------------------------------- 1 | import { buildPattern } from "./debugStatement.test"; 2 | import { getVariables } from "./debugVariableUtil"; 3 | 4 | test('findObjectsOfACertainClass for a Pattern', () => { 5 | 6 | let pattern = buildPattern(); 7 | 8 | let variables = getVariables(pattern); 9 | expect(variables).toStrictEqual(['person','acted_in','movie','directed','director']) 10 | }) 11 | 12 | -------------------------------------------------------------------------------- /ui/src/dataModel/graphDataConstants.js: -------------------------------------------------------------------------------- 1 | 2 | export const GraphChangeType = { 3 | AddOrUpdateNode: "AddOrUpdateNode", 4 | AddOrUpdateRelationship: "AddOrUpdateRelationship", 5 | AddOrUpdateProperty: "AddOrUpdateProperty", 6 | RemoveNode: "RemoveNode", 7 | RemoveRelationship: "RemoveRelationship", 8 | RemoveProperty: "RemoveProperty" 9 | } 10 | 11 | export const GraphViewChangeType = { 12 | AddOrUpdateNode: "AddOrUpdateNode", 13 | AddOrUpdateRelationship: "AddOrUpdateRelationship", 14 | RemoveNode: "RemoveNode", 15 | RemoveRelationship: "RemoveRelationship", 16 | NodeDisplayUpdate: "NodeDisplayUpdate", 17 | CanvasTransformUpdate: "CanvasTransformUpdate" 18 | } 19 | 20 | export const GraphDocChangeType = { 21 | PanelResize: "GraphDocPanelResize", 22 | PanelOpen: "GraphDocPanelOpen", 23 | PanelClose: "GraphDocPanelClose", 24 | ActiveTabChanged: "ActiveTabChanged", 25 | ExportSettingsChanged: "ExportSettingsChanged" 26 | }; 27 | 28 | export const NodeLabels = { 29 | GraphView: "GraphView", 30 | GraphDoc: "GraphDoc" 31 | } 32 | 33 | export const RelationshipTypes = { 34 | DEFAULT_GRAPH_VIEW_FOR: "DEFAULT_GRAPH_VIEW_FOR", 35 | GRAPH_VIEW_FOR: "GRAPH_VIEW_FOR" 36 | } 37 | 38 | export const MINI_GRAPH_DOC_SUBGRAPH_MODEL = { 39 | primaryNodeLabel: NodeLabels.GraphDoc, 40 | keyConfig: [ 41 | {nodeLabel: NodeLabels.GraphDoc, propertyKeys: ["key"]} 42 | ] 43 | } -------------------------------------------------------------------------------- /ui/src/dataModel/helper.js: -------------------------------------------------------------------------------- 1 | 2 | export function smartQuote (text) { 3 | if (text && text.match && text.match(/ /)) { 4 | return '`' + text + '`'; 5 | } else { 6 | return text; 7 | } 8 | } 9 | 10 | export function lowerFirstLetter (text) { 11 | if (text && text.length > 0 && typeof(text) === 'string') { 12 | return text.substring(0,1).toLowerCase() + text.substring(1); 13 | } else { 14 | return text; 15 | } 16 | } -------------------------------------------------------------------------------- /ui/src/dataModel/neo4jdb/constraintIndexTypes.js: -------------------------------------------------------------------------------- 1 | 2 | export const ConstraintTypes = { 3 | RelationshipPropertyExistence: "RELATIONSHIP PROPERTY EXISTENCE", 4 | NodePropertyExistence: "NODE PROPERTY EXISTENCE", 5 | NodeKey: "NODE KEY", 6 | Uniqueness: "UNIQUENESS" 7 | } 8 | 9 | export const IndexTypes = { 10 | SinglePropertyIndex: "SinglePropertyIndex", 11 | CompositePropertyIndex: "CompositePropertyIndex" 12 | } 13 | 14 | export const getConstraintTypeKey = (desc) => Object.keys(ConstraintTypes).find(key => ConstraintTypes[key] === desc); 15 | 16 | -------------------------------------------------------------------------------- /ui/src/dataModel/neo4jdb/indexes.js: -------------------------------------------------------------------------------- 1 | 2 | import { getNodeLabelDataModel } from './dataModelHelper'; 3 | import { IndexTypes } from './constraintIndexTypes'; 4 | 5 | export const getIndexDataModel = (info) => { 6 | const { indexName, nodeLabelString, propertyNameArray } = info; 7 | if (propertyNameArray.length > 1) { 8 | // its a composite index 9 | var dataModel = getNodeLabelDataModel(nodeLabelString, propertyNameArray, IndexTypes.CompositePropertyIndex); 10 | var nodeLabel = dataModel.getNodeLabelArray()[0]; 11 | nodeLabel.addIndex({ 12 | indexName: indexName, 13 | propertyDefinitionKeys: propertyNameArray 14 | }); 15 | return dataModel; 16 | } else { 17 | return getNodeLabelDataModel(nodeLabelString, propertyNameArray, IndexTypes.SinglePropertyIndex); 18 | } 19 | } 20 | 21 | export const handleNeo3XRow = (row) => { 22 | if (row.type === 'node_label_property') { 23 | return getIndexDataModel({ 24 | indexName: row.indexName, 25 | nodeLabelString: row.tokenNames[0], 26 | propertyNameArray: row.properties 27 | }); 28 | } else { 29 | return null; 30 | } 31 | } 32 | 33 | export const handleNeo4XRow = (row) => { 34 | if (row.type !== 'FULLTEXT' && row.uniqueness === 'NONUNIQUE') { 35 | return getIndexDataModel({ 36 | indexName: row.name, 37 | nodeLabelString: row.labelsOrTypes[0], 38 | propertyNameArray: row.properties 39 | }); 40 | } else { 41 | return null; 42 | } 43 | } 44 | 45 | export const processDbIndexesRow = (row) => (row.tokenNames) ? handleNeo3XRow(row) : handleNeo4XRow(row); 46 | 47 | export const processDbIndexesResult = (dbIndexesRows) => { 48 | 49 | var dataModels = []; 50 | if (Array.isArray(dbIndexesRows)) { 51 | dataModels = dbIndexesRows 52 | .map(row => processDbIndexesRow(row)) 53 | .filter(dataModel => dataModel); // get rid of nulls 54 | } 55 | return dataModels; 56 | } -------------------------------------------------------------------------------- /ui/src/dataModel/vsCodeDebugHelper.js: -------------------------------------------------------------------------------- 1 | 2 | // I can't seem to run the VS Code debugger through Jest 3 | // therefore I'm making this file so I can run a specific test and stop it in the debugger 4 | 5 | import { CypherVariableScope } from './cypherVariableScope'; 6 | import { CypherStatementBuilder } from './cypherStatementBuilder'; 7 | 8 | import { WhereClause, whereItem } from './cypherWhere'; 9 | import { testConvertCypherAndReturnStrings } from './cypherStringConverter.test'; 10 | import CypherClause from './cypherClause'; 11 | import SubQueryClause from './cypherSubQuery'; 12 | import { ReturnClause } from './cypherReturn'; 13 | import { buildPattern, buildOneNodePattern, buildNodeRelNodePattern } from './debugStatement.test'; 14 | 15 | const cy = new CypherStatementBuilder({ 16 | variableScope: new CypherVariableScope() 17 | }); 18 | 19 | const test = (desc, functionToTest) => { 20 | console.log(`running ${desc}`); 21 | functionToTest(); 22 | } 23 | 24 | const runTest = () => { 25 | 26 | // test ('test QPP', () => { 27 | // testConvertCypherAndReturnStrings("MATCH ((x)-[r]->(z) WHERE z.p > x.p){2,3}"); 28 | // }); 29 | 30 | test('get subQuery debug snippets', () => { 31 | var subQueryClause = new SubQueryClause() 32 | var returnBody = new ReturnClause({ limit: ''}); 33 | returnBody 34 | .item('person', 'name') 35 | 36 | var returnClause = new CypherClause({ 37 | keyword: 'RETURN', 38 | clauseInfo: returnBody 39 | }); 40 | 41 | // var pattern = buildOneNodePattern(); 42 | var pattern = buildNodeRelNodePattern(); 43 | var cypherMatchClause = new CypherClause({ 44 | keyword: 'MATCH', 45 | clauseInfo: pattern 46 | }); 47 | 48 | var withClause = new CypherClause({ keyword: 'WITH', clauseInfo: 'x' }); 49 | subQueryClause.clauses = [withClause, cypherMatchClause, returnClause]; 50 | 51 | var snippetSet = subQueryClause.getDebugCypherSnippetSet(); 52 | }) 53 | 54 | } 55 | 56 | runTest(); 57 | -------------------------------------------------------------------------------- /ui/src/dynamicConfig.js: -------------------------------------------------------------------------------- 1 | 2 | export const getDynamicConfigValue = (envKey) => { 3 | 4 | var value = null; 5 | if (window._dynamicEnv_) { 6 | value = window._dynamicEnv_[envKey]; 7 | } 8 | if (!window._dynamicEnv_ || value === undefined) { 9 | value = process.env[envKey]; 10 | } 11 | return value; 12 | } 13 | 14 | export const getAppName = () => getDynamicConfigValue('REACT_APP_APP_NAME') || 'Cypher Workbench'; 15 | export const getAppTrackingName = () => getDynamicConfigValue('REACT_APP_TRACKING_APP_NAME') || 'CypherWorkbench'; 16 | export const getAppNameFontSize = () => getDynamicConfigValue('REACT_APP_APP_NAME_FONT_SIZE') || '1em'; 17 | 18 | 19 | -------------------------------------------------------------------------------- /ui/src/homePage/components/SettingsForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { 3 | Divider, 4 | MenuItem, 5 | Typography 6 | } from '@material-ui/core'; 7 | import { OutlinedStyledButton } from '../../components/common/Components'; 8 | 9 | export default class SettingsForm extends Component { 10 | 11 | constructor () { 12 | super(); 13 | } 14 | 15 | clearStorageAndLogout = () => { 16 | const { logout } = this.props; 17 | localStorage.clear(); 18 | logout(); 19 | } 20 | 21 | render () { 22 | const { email, currentUser, logout, showSwitchOrganizations } = this.props; 23 | 24 | var howManyOrganizations = (currentUser && currentUser.authorizedOrganizations) ? currentUser.authorizedOrganizations.length : 0; 25 | var primaryOrganization = (currentUser && currentUser.primaryOrganization) ? currentUser.primaryOrganization : 'Unknown'; 26 | 27 | return ( 28 |
29 | 30 | Signed in as
{email} 31 |
32 | {(howManyOrganizations > 1) && 33 | <> 34 | 35 | 36 | Organization {primaryOrganization} 37 | 39 | Switch 40 | 41 | 42 | 43 | } 44 | 45 | Clear Local Storage and Logout 46 | Logout 47 |
48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ui/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import * as serviceWorker from "./serviceWorker"; 6 | import { BrowserRouter as Router } from "react-router-dom"; 7 | 8 | const Main = () => ( 9 | 10 | 11 | 12 | ); 13 | 14 | ReactDOM.render(
, document.getElementById("root")); 15 | 16 | // If you want your app to work offline and load faster, you can change 17 | // unregister() to register() below. Note this comes with some pitfalls. 18 | // Learn more about service workers: https://bit.ly/CRA-PWA 19 | serviceWorker.unregister(); 20 | -------------------------------------------------------------------------------- /ui/src/persistence/LocalPersistence.js: -------------------------------------------------------------------------------- 1 | 2 | var DATA_MODEL_METADATA_KEY = 'dataModelMetadata'; 3 | 4 | export function saveLocalDataModel (modelInfo, dataModel) { 5 | var modelKey = modelInfo.key; 6 | //console.log("canvasViewSettings: " + JSON.stringify(modelInfo.canvasViewSettings)); 7 | var modelsJson = localStorage.getItem(DATA_MODEL_METADATA_KEY); 8 | var models = {}; 9 | if (modelsJson) { 10 | models = JSON.parse(modelsJson); 11 | } 12 | models[modelKey] = modelInfo; 13 | localStorage.setItem(DATA_MODEL_METADATA_KEY, JSON.stringify(models)); 14 | 15 | var dataModelString = dataModel.toJSON(); 16 | //console.log(dataModel.toJSON(true)); 17 | localStorage.setItem(modelKey, dataModelString); 18 | } 19 | 20 | export function loadLocalDataModelMetadata () { 21 | var modelsJson = localStorage.getItem(DATA_MODEL_METADATA_KEY); 22 | var models = {}; 23 | if (modelsJson) { 24 | models = JSON.parse(modelsJson); 25 | } 26 | return models; 27 | } 28 | 29 | export function getLocalDataModelJSON (modelInfoKey) { 30 | var dataModelJSON = localStorage.getItem(modelInfoKey); 31 | return dataModelJSON; 32 | } 33 | 34 | export function deleteLocalDataModel (modelInfoKey) { 35 | var modelsJson = localStorage.getItem(DATA_MODEL_METADATA_KEY); 36 | var models = {}; 37 | if (modelsJson) { 38 | models = JSON.parse(modelsJson); 39 | delete models[modelInfoKey]; 40 | } 41 | localStorage.setItem(DATA_MODEL_METADATA_KEY, JSON.stringify(models)); 42 | localStorage.removeItem(modelInfoKey); 43 | } 44 | -------------------------------------------------------------------------------- /ui/src/persistence/graphql/GraphQLKeymaker.js: -------------------------------------------------------------------------------- 1 | import { gql } from "@apollo/client"; 2 | import { getClient, handleError } from "./GraphQLPersistence"; 3 | 4 | export function editKeymakerPhase( 5 | phaseId, 6 | cypherQuery, 7 | cypherWorkbenchCypherBuilderKey, 8 | callback, 9 | doTokenExpiredErrorHandling = true 10 | ) { 11 | getClient() 12 | .query({ 13 | query: gql` 14 | mutation EditPhase($id: ID!, $input: EditPhaseInput!) { 15 | editPhase(id: $id, input: $input) { 16 | id 17 | } 18 | } 19 | `, 20 | variables: { 21 | id: phaseId, 22 | input: { 23 | cypherQuery, 24 | cypherWorkbenchCypherBuilderKey, 25 | }, 26 | }, 27 | }) 28 | .then((result) => { 29 | callback({ success: true, data: result.data.editKeymakerPhase }); 30 | }) 31 | .catch((error) => { 32 | console.log(error); 33 | handleError( 34 | editKeymakerPhase, 35 | arguments, 36 | callback, 37 | error, 38 | doTokenExpiredErrorHandling 39 | ); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /ui/src/persistence/graphql/authGraphql.js: -------------------------------------------------------------------------------- 1 | import { ApolloClient } from "@apollo/client/core"; 2 | import { InMemoryCache } from '@apollo/client/cache'; 3 | import { HttpLink } from '@apollo/client'; 4 | 5 | const defaultOptions = { 6 | watchQuery: { 7 | fetchPolicy: 'no-cache', 8 | }, 9 | query: { 10 | fetchPolicy: 'no-cache', 11 | } 12 | } 13 | 14 | function getLink (uri, authorizationInfo) { 15 | var headers = {}; 16 | if (authorizationInfo) { 17 | headers.authorization = authorizationInfo; 18 | } 19 | return new HttpLink({ 20 | uri: uri, 21 | headers: headers 22 | }); 23 | } 24 | 25 | export const getGraqhQLConnection = (uri, authorizationInfo) => { 26 | const cache = new InMemoryCache({ 27 | resultCaching: false 28 | }); 29 | 30 | return new ApolloClient({ 31 | cache: cache, 32 | link: getLink(uri, authorizationInfo), 33 | defaultOptions: { ...defaultOptions } 34 | }); 35 | 36 | } -------------------------------------------------------------------------------- /ui/src/persistence/graphql/dbConnection.js: -------------------------------------------------------------------------------- 1 | import { gql } from "@apollo/client"; 2 | 3 | const ALL_DB_CONNECTIONS_FOR_USER = gql` 4 | query { 5 | dbConnections: allDBConnectionsForUser { 6 | id 7 | name 8 | url 9 | databaseName 10 | encrypted 11 | proxyThroughAppServer 12 | labels 13 | isPrivate 14 | canCurrentUserEdit 15 | canCurrentUserDelete 16 | dbInfo { 17 | isConnected 18 | license 19 | versions 20 | hasApoc 21 | } 22 | users { 23 | role 24 | email 25 | picture 26 | } 27 | } 28 | } 29 | `; 30 | 31 | const CREATE_DB_CONNECTION = gql` 32 | mutation CreateDBConnection($input: CreateDBConnectionInput) { 33 | dbConnection: createDBConnection(input: $input) { 34 | id 35 | } 36 | } 37 | `; 38 | 39 | const EDIT_DB_CONNECTION = gql` 40 | mutation EditDBConnection($id: ID!, $properties: EditDBConnectionInput) { 41 | dbConnection: editDBConnection(id: $id, properties: $properties) { 42 | id 43 | } 44 | } 45 | `; 46 | 47 | const DELETE_DB_CONNECTION = gql` 48 | mutation DeleteDBConnection($id: ID!) { 49 | dbConnection: deleteDBConnection(id: $id) { 50 | id 51 | } 52 | } 53 | `; 54 | 55 | const GET_DB_PROPERTIES = gql` 56 | query GetDBProperties($id: ID!, $label: String!) { 57 | dbConnection(id: $id) { 58 | propertyNames(label: $label) 59 | } 60 | } 61 | `; 62 | 63 | export { 64 | ALL_DB_CONNECTIONS_FOR_USER, 65 | CREATE_DB_CONNECTION, 66 | EDIT_DB_CONNECTION, 67 | DELETE_DB_CONNECTION, 68 | GET_DB_PROPERTIES 69 | }; 70 | -------------------------------------------------------------------------------- /ui/src/persistence/graphql/getBrandingInfo.js: -------------------------------------------------------------------------------- 1 | import { getGraqhQLConnection } from './authGraphql'; 2 | import { gql } from "@apollo/client"; 3 | 4 | const showError = (error) => { 5 | const messageString = 'Error fetching branding info'; 6 | console.log(messageString, error); 7 | //alert(`${messageString}: ${JSON.stringify(error)}`); 8 | } 9 | 10 | export const fetchBrandingInfo = () => { 11 | 12 | return new Promise((resolve, reject) => { 13 | const uri = process.env.REACT_APP_HIVE_URI; 14 | const token = localStorage.getItem("id_token"); 15 | var authorization = token ? `Bearer ${token}` : '' 16 | var graphQLConnection = getGraqhQLConnection(uri, authorization); 17 | graphQLConnection.query({ 18 | query: gql` 19 | query GetBrandingInfo { 20 | brandingInfo:getBrandingInfo { 21 | logourl 22 | logoheight 23 | primaryColor 24 | secondaryColor 25 | } 26 | } 27 | ` 28 | }) 29 | .then((result) => { 30 | const { data, errors } = result; 31 | if (errors) { 32 | showError(errors) 33 | reject(); 34 | } 35 | if (!data.brandingInfo) { 36 | showError('brandingInfo is not in response') 37 | reject(); 38 | } 39 | resolve(data.brandingInfo); 40 | }) 41 | .catch((error) => { 42 | showError(error) 43 | reject(); 44 | }); 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /ui/src/persistence/graphql/user.js: -------------------------------------------------------------------------------- 1 | import { gql } from "@apollo/client"; 2 | 3 | const GET_CURRENT_USER = gql` 4 | query GetCurrentUser { 5 | user: getCurrentUser { 6 | name 7 | email 8 | picture 9 | primaryOrganization 10 | authorizedOrganizations 11 | browserAsymmetricEncryptionKey 12 | } 13 | } 14 | `; 15 | 16 | const DECRYPT_ASYMMETRIC_ENCRYPTED_ITEMS = gql` 17 | query DecryptAsymmetricEncryptedItems ($items: [EncryptedItemWithPublicKey]) { 18 | decryptedItems: decryptAsymmetricEncryptedItems (items: $items) 19 | } 20 | `; 21 | 22 | const GET_SYSTEM_MESSAGES = gql` 23 | query GetSystemMessages { 24 | messages: getSystemMessages { 25 | key 26 | message 27 | validUntil 28 | } 29 | } 30 | `; 31 | 32 | const ACKNOWLEDGE_MESSAGES = gql` 33 | mutation AcknowledgeMessages ($messageKeys: [String]) { 34 | acknowledgeMessages (messageKeys: $messageKeys) 35 | } 36 | `; 37 | 38 | export { 39 | GET_CURRENT_USER, 40 | DECRYPT_ASYMMETRIC_ENCRYPTED_ITEMS, 41 | GET_SYSTEM_MESSAGES, 42 | ACKNOWLEDGE_MESSAGES 43 | }; 44 | -------------------------------------------------------------------------------- /ui/src/tools/common/DocumentSecurityRole.js: -------------------------------------------------------------------------------- 1 | import { USER_ROLE, ALERT_TYPES } from '../../common/Constants'; 2 | 3 | export default class DocumentSecurityRole { 4 | 5 | role = null; 6 | 7 | constructor (props) { 8 | this.props = props; 9 | } 10 | 11 | setRole = (role) => this.role = role; 12 | 13 | getRole = () => this.role; 14 | 15 | getRoleDisplay = (role) => { 16 | role = (role) ? role : this.role; 17 | if (role === USER_ROLE.MEMBER) { 18 | return 'EDITOR'; 19 | } else { 20 | return (role) ? role : 'Unknown'; 21 | } 22 | } 23 | 24 | checkRole = (allowedRoles, showMessage) => { 25 | // if a single role is passed in, convert it to an array 26 | allowedRoles = (typeof(allowedRoles) === 'string') ? [allowedRoles] : allowedRoles; 27 | const role = this.getRole(); 28 | if (allowedRoles.includes(role)) { 29 | return true; 30 | } else { 31 | if (showMessage) { 32 | alert('Insufficient permission, your role is ' + this.getRoleDisplay(), ALERT_TYPES.WARNING); 33 | } 34 | return false; 35 | } 36 | } 37 | 38 | canEdit = (showMessage) => 39 | this.checkRole([USER_ROLE.OWNER, USER_ROLE.MEMBER], showMessage); 40 | 41 | 42 | isOwner = (showMessage) => 43 | this.checkRole([USER_ROLE.OWNER], showMessage); 44 | 45 | canEditShowMessage = () => 46 | this.checkRole([USER_ROLE.OWNER, USER_ROLE.MEMBER], true); 47 | } 48 | -------------------------------------------------------------------------------- /ui/src/tools/common/SecurityRole.js: -------------------------------------------------------------------------------- 1 | 2 | import { USER_ROLE, ALERT_TYPES } from '../../common/Constants'; 3 | 4 | export const SecurityMessages = { 5 | NoPermissionGeneral: "You do not have permission", 6 | NoPermissionToRemove: "You do not have permission to remove this", 7 | NoPermissionToEdit: "You do not have permission to edit this", 8 | } 9 | 10 | class SecurityRoleHelper { 11 | 12 | role = null; 13 | 14 | setRole = (role) => { 15 | this.role = role; 16 | } 17 | 18 | getRole = () => { 19 | return this.role; 20 | } 21 | 22 | getRoleDisplay = (role) => { 23 | role = (role) ? role : this.role; 24 | if (role === USER_ROLE.MEMBER) { 25 | return 'EDITOR'; 26 | } else { 27 | return (role) ? role : 'Unknown'; 28 | } 29 | } 30 | 31 | checkRole = (allowedRoles, showMessage) => { 32 | // if a single role is passed in, convert it to an array 33 | allowedRoles = (typeof(allowedRoles) === 'string') ? [allowedRoles] : allowedRoles; 34 | if (allowedRoles.includes(this.role)) { 35 | return true; 36 | } else { 37 | if (showMessage) { 38 | alert('Insufficient permission, your role is ' + this.getRoleDisplay(), ALERT_TYPES.WARNING); 39 | } 40 | return false; 41 | } 42 | } 43 | 44 | canEdit = (showMessage) => { 45 | return this.checkRole([USER_ROLE.OWNER, USER_ROLE.MEMBER], showMessage); 46 | } 47 | 48 | isOwner = (showMessage) => { 49 | return this.checkRole([USER_ROLE.OWNER], showMessage); 50 | } 51 | 52 | canEditShowMessage = () => { 53 | return this.checkRole([USER_ROLE.OWNER, USER_ROLE.MEMBER], true); 54 | } 55 | } 56 | 57 | const SecurityRole = new SecurityRoleHelper(); 58 | export default SecurityRole; 59 | -------------------------------------------------------------------------------- /ui/src/tools/common/WorkStatus.js: -------------------------------------------------------------------------------- 1 | 2 | export const WorkStatus = { 3 | Complete: "Complete", 4 | Pending: "Pending" 5 | } -------------------------------------------------------------------------------- /ui/src/tools/common/mapping/ResultExportDialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Dialog, DialogActions, 4 | DialogContent, DialogTitle 5 | } from '@material-ui/core'; 6 | import { StyledButton } from '../../../components/common/Components'; 7 | import BigQueryDestination from './bigquery/BigQueryDestination'; 8 | import { makeStyles } from '@material-ui/core/styles'; 9 | 10 | export const ResultExportTypes = { 11 | BigQuery: "BigQuery" 12 | } 13 | 14 | export default function ResultExportDialog(props) { 15 | 16 | const useStyles = makeStyles(() => ({ 17 | paper: { width: '50em' }, 18 | })); 19 | const myclasses = useStyles(); 20 | 21 | const closeFunc = props.onClose || props.handleClose; 22 | 23 | var exportType = No export type configured 24 | switch (props.exportType) { 25 | case ResultExportTypes.BigQuery: 26 | exportType = ; 30 | break; 31 | } 32 | 33 | return ( 34 | 42 | {props.title} 43 | 44 | {exportType} 45 | 46 | 47 | {props.buttons.map((button, index) => { 48 | return ( 49 | button.onClick(button, index, closeFunc)} color="primary" autoFocus={button.autofocus}> 50 | {button.text} 51 | 52 | ) 53 | })} 54 | 55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /ui/src/tools/common/mapping/bigquery/BigQueryUtil.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | ColumnDefinition, 4 | TableDefinition, 5 | TableCatalog 6 | } from '../../../../dataModel/dataSource/generalDataSource'; 7 | 8 | export const getTableCatalogFromDatasetSchemaResponse = (datasetSchemaResponse, datasetId) => { 9 | 10 | /* Example schemaRow 11 | { 12 | "table_catalog": "big-query-project-id", 13 | "table_schema": "TableSchema", 14 | "table_name": "clients", 15 | "column_name": "id", 16 | "ordinal_position": 1, 17 | "is_nullable": "YES", 18 | "data_type": "STRING", 19 | "is_hidden": "NO", 20 | "is_system_defined": "NO", 21 | "is_partitioning_column": "NO" 22 | }, 23 | */ 24 | var tables = {}; 25 | 26 | datasetSchemaResponse.map(schemaRow => { 27 | 28 | var table = tables[schemaRow.table_name]; 29 | var columnDefinitions = []; 30 | 31 | if (!table) { 32 | tables[schemaRow.table_name] = new TableDefinition({ 33 | key: schemaRow.table_name, 34 | name: schemaRow.table_name, 35 | columnDefinitions: columnDefinitions, 36 | //constraints, 37 | //metadata 38 | }); 39 | } else { 40 | columnDefinitions = table.columnDefinitions; 41 | } 42 | 43 | var newColDefinition = new ColumnDefinition({ 44 | key: schemaRow.column_name, 45 | name: schemaRow.column_name, 46 | ordinalPosition: schemaRow.ordinal_position, 47 | datatype: schemaRow.data_type 48 | // constraints 49 | // metadata 50 | }); 51 | columnDefinitions.push(newColDefinition); 52 | }); 53 | 54 | var tableCatalog = new TableCatalog({ 55 | key: datasetId, 56 | name: datasetId, 57 | tableDefinitions: Object.values(tables) 58 | }); 59 | return tableCatalog; 60 | } -------------------------------------------------------------------------------- /ui/src/tools/common/mapping/dataSourceTableBlock.js: -------------------------------------------------------------------------------- 1 | 2 | export default class DataSourceTableBlock { 3 | constructor (properties) { 4 | properties = properties || {}; 5 | 6 | var { 7 | key, 8 | title, 9 | expanded, 10 | selected, 11 | showToggleTool, 12 | scrollIntoView, 13 | ref, 14 | blockElement, 15 | graphNode, 16 | dataProvider 17 | } = properties; 18 | 19 | this.key = key; 20 | this.title = title; 21 | this.expanded = (expanded) ? true : false; 22 | this.selected = (selected) ? true : false; 23 | this.showToggleTool = (showToggleTool) ? true : false; 24 | this.scrollIntoView = (scrollIntoView) ? true : false; 25 | this.ref = ref; 26 | this.blockElement = blockElement; 27 | this.graphNode = graphNode; 28 | this.dataProvider = dataProvider; 29 | } 30 | 31 | getWorkStatus = () => (this.dataProvider && this.dataProvider.getWorkStatus) 32 | ? this.dataProvider.getWorkStatus() : null; 33 | 34 | getWorkMessage = () => (this.dataProvider && this.dataProvider.getWorkMessage) 35 | ? this.dataProvider.getWorkMessage() : null; 36 | } 37 | -------------------------------------------------------------------------------- /ui/src/tools/common/toolConstants.js: -------------------------------------------------------------------------------- 1 | 2 | export const SAVE_MODE = { 3 | TOTALLY_NEW: "TotallyNew", 4 | NEW: "New", 5 | EXISTS: "Exists" 6 | }; 7 | 8 | export const RELATIONSHIP_DISPLAY = { 9 | NORMAL: 'normal', 10 | MEDIUM: 'medium', 11 | LIGHT: 'light' 12 | } -------------------------------------------------------------------------------- /ui/src/tools/common/util.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export var ensureProperties = (callerName, properties, propertyName) => { 4 | if (!properties || !properties[propertyName]) { 5 | throw new Error(`${callerName}: properties and properties.${propertyName} must be specified`); 6 | } 7 | } 8 | 9 | 10 | export var tryAgain = (func, howManyTimes, delayBetweenTries) => { 11 | var howManyTimesTried = 0; 12 | const localFunc = () => { 13 | howManyTimesTried++; 14 | if (howManyTimesTried <= howManyTimes) { 15 | setTimeout(() => { func() }, delayBetweenTries) 16 | } 17 | } 18 | localFunc(); 19 | } -------------------------------------------------------------------------------- /ui/src/tools/common/validation/ValidationStatus.js: -------------------------------------------------------------------------------- 1 | 2 | export const ValidationStatus = { 3 | Valid: "Valid", 4 | Invalid: "Invalid", 5 | NotValidated: "NotValidated", 6 | ValidationInProgress: "ValidationInProgress", 7 | Error: "Error" 8 | } 9 | -------------------------------------------------------------------------------- /ui/src/tools/toolCypherBuilder/components/dataProvider/blockUpdateTypes.js: -------------------------------------------------------------------------------- 1 | 2 | export const BlockUpdateType = { 3 | SaveObjectReloaded: "SaveObjectReloaded", 4 | 5 | BlockRemoved: "BlockRemoved", 6 | 7 | NodePatternAdded: "NodePatternAdded", 8 | NodePatternRemoved: "NodePatternRemoved", 9 | NodePatternUpdateNodeLabels: "NodePatternUpdateNodeLabels", 10 | NodePatternUpdateVariable: "NodePatternUpdateVariable", 11 | 12 | RelationshipPatternAdded: "RelationshipPatternAdded", 13 | RelationshipPatternRemoved: "RelationshipPatternRemoved", 14 | RelationshipPatternUpdateTypes: "RelationshipPatternUpdateTypes", 15 | RelationshipPatternUpdateVariable: "RelationshipPatternUpdateVariable", 16 | RelationshipPatternUpdateRange: "RelationshipPatternUpdateRange", 17 | 18 | ReverseRelationshipPattern: "ReverseRelationshipPattern", 19 | 20 | WithVariablesChanged: "WithVariablesChanged", 21 | 22 | ReturnItemsChanged: "ReturnItemsChanged", 23 | 24 | ScopeChange: "ScopeChange" 25 | } 26 | 27 | export default BlockUpdateType; -------------------------------------------------------------------------------- /ui/src/tools/toolCypherBuilder/components/dataProvider/cypherBlock.js: -------------------------------------------------------------------------------- 1 | 2 | export default class CypherBlock { 3 | constructor (properties) { 4 | properties = properties || {}; 5 | 6 | var { 7 | key, 8 | title, 9 | expanded, 10 | selected, 11 | showToggleTool, 12 | scrollIntoView, 13 | keyword, 14 | ref, 15 | blockElement, 16 | graphNode, 17 | dataProvider 18 | } = properties; 19 | 20 | this.key = key; 21 | this.title = title; 22 | this.expanded = (expanded) ? true : false; 23 | this.selected = (selected) ? true : false; 24 | this.showToggleTool = (showToggleTool) ? true : false; 25 | this.scrollIntoView = (scrollIntoView) ? true : false; 26 | this.keyword = keyword; 27 | this.ref = ref; 28 | this.blockElement = blockElement; 29 | this.graphNode = graphNode; 30 | this.dataProvider = dataProvider; 31 | } 32 | 33 | getCypher = () => { 34 | if (this.dataProvider && this.dataProvider.getCypher) { 35 | return this.dataProvider.getCypher(); 36 | } else { 37 | return `// TODO: getCypher for block ${this.title}`; 38 | } 39 | } 40 | 41 | getDebugCypherSnippets = () => { 42 | if (this.dataProvider && this.dataProvider.getDebugCypherSnippets) { 43 | return this.dataProvider.getDebugCypherSnippets(); 44 | } else { 45 | return null; 46 | } 47 | } 48 | 49 | isWith = () => { 50 | if (this.dataProvider && this.dataProvider.getCypherKeyword) { 51 | if (this.dataProvider.getCypherKeyword() === 'WITH') { 52 | return true; 53 | } 54 | } 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ui/src/tools/toolCypherBuilder/components/dataProvider/limitClauseDataProvider.js: -------------------------------------------------------------------------------- 1 | 2 | import DataTypes from '../../../../dataModel/DataTypes'; 3 | import { LimitClause, LIMIT_CLAUSE_DEFAULT_LIMIT } from '../../../../dataModel/cypherLimit'; 4 | import { CypherBlockKeywords } from '../../CypherBuilderRefactor'; 5 | 6 | export class LimitClauseDataProvider { 7 | 8 | constructor (properties) { 9 | properties = (properties) ? properties : {}; 10 | 11 | var { 12 | cypherBlockDataProvider, 13 | cypherBlockKey, 14 | cypherBuilder, 15 | graphNode, 16 | limitClause, 17 | ref 18 | } = properties; 19 | 20 | this.graphNode = graphNode; 21 | this.cypherBlockDataProvider = cypherBlockDataProvider; 22 | this.cypherBlockKey = cypherBlockKey; 23 | this.cypherBuilder = cypherBuilder; 24 | this.limitClause = (limitClause) ? limitClause : new LimitClause(); 25 | this.ref = ref; 26 | 27 | this.loadDataIfPresent(); 28 | } 29 | 30 | data = () => this.cypherBlockDataProvider.data(); 31 | 32 | getScopedBlockProvider = () => this.cypherBlockDataProvider.getScopedBlockProvider(this.cypherBlockKey); 33 | 34 | getLimitClause = () => this.limitClause; 35 | 36 | loadDataIfPresent = () => { 37 | var limit = this.graphNode.getPropertyValueByKey('limit', LIMIT_CLAUSE_DEFAULT_LIMIT); 38 | this.limitClause.setLimit(limit); 39 | } 40 | 41 | getLimit = () => this.limitClause.getLimit(); 42 | setLimit = (limit) => { 43 | this.limitClause.setLimit(limit); 44 | this.graphNode.addOrUpdateProperty("limit", limit, DataTypes.String); 45 | 46 | // update cypher 47 | this.cypherBuilder.updateCypher(); 48 | } 49 | 50 | getCypher = () => this.limitClause.toCypherString(); 51 | getCypherKeyword = () => CypherBlockKeywords.LIMIT; 52 | 53 | handleBlockUpdated = (args) => { 54 | // nothing to do 55 | } 56 | } -------------------------------------------------------------------------------- /ui/src/tools/toolCypherBuilder/components/dataProvider/skipClauseDataProvider.js: -------------------------------------------------------------------------------- 1 | 2 | import DataTypes from '../../../../dataModel/DataTypes'; 3 | import { SkipClause, SKIP_CLAUSE_DEFAULT_SKIP } from '../../../../dataModel/cypherSkip'; 4 | import { CypherBlockKeywords } from '../../CypherBuilderRefactor'; 5 | 6 | export class SkipClauseDataProvider { 7 | 8 | constructor (properties) { 9 | properties = (properties) ? properties : {}; 10 | 11 | var { 12 | cypherBlockDataProvider, 13 | cypherBlockKey, 14 | cypherBuilder, 15 | graphNode, 16 | skipClause, 17 | ref 18 | } = properties; 19 | 20 | this.graphNode = graphNode; 21 | this.cypherBlockDataProvider = cypherBlockDataProvider; 22 | this.cypherBlockKey = cypherBlockKey; 23 | this.cypherBuilder = cypherBuilder; 24 | this.skipClause = (skipClause) ? skipClause : new SkipClause(); 25 | this.ref = ref; 26 | 27 | this.loadDataIfPresent(); 28 | } 29 | 30 | data = () => this.cypherBlockDataProvider.data(); 31 | 32 | getScopedBlockProvider = () => this.cypherBlockDataProvider.getScopedBlockProvider(this.cypherBlockKey); 33 | 34 | getSkipClause = () => this.skipClause; 35 | 36 | loadDataIfPresent = () => { 37 | var skip = this.graphNode.getPropertyValueByKey('skip', SKIP_CLAUSE_DEFAULT_SKIP); 38 | this.skipClause.setSkip(skip); 39 | } 40 | 41 | getSkip = () => this.skipClause.getSkip(); 42 | setSkip = (skip) => { 43 | this.skipClause.setSkip(skip); 44 | this.graphNode.addOrUpdateProperty("skip", skip, DataTypes.String); 45 | 46 | // update cypher 47 | this.cypherBuilder.updateCypher(); 48 | } 49 | 50 | getCypher = () => this.skipClause.toCypherString(); 51 | getCypherKeyword = () => CypherBlockKeywords.SKIP; 52 | 53 | handleBlockUpdated = (args) => { 54 | // nothing to do 55 | } 56 | } -------------------------------------------------------------------------------- /ui/src/tools/toolCypherDebug/components/Constants.js: -------------------------------------------------------------------------------- 1 | 2 | export const LayoutOptions = { 3 | ForceDirected: 'forceDirected', 4 | Hierarchical: 'hierarchical', 5 | Free: 'free', 6 | Grid: 'grid' 7 | } 8 | 9 | export const HierarchicalOptions = { 10 | DirectionLeft: 'left', 11 | DirectionRight: 'right', 12 | DirectionUp: 'up', 13 | DirectionDown: 'down', 14 | PackingBin: 'bin', 15 | PackingStack: 'stack' 16 | } 17 | 18 | export const BloomColors = { 19 | yellow: "#FFE081", 20 | mauve: "#C990C0", 21 | orange: "#F79767", 22 | lightBlue: "#57C7E3", 23 | red: "#F16667", 24 | tan: "#D9C8AE", 25 | lightGreen: "#8DCC93", 26 | pink: "#ECB5C9", 27 | blue: "#4C8EDA", 28 | peach: "#FFC454", 29 | darkPink: "#DA7194", 30 | green: "#569480", 31 | grey: "#848484", 32 | lightGrey: "#D9D9D9" 33 | } 34 | -------------------------------------------------------------------------------- /ui/src/tools/toolCypherDebug/components/MiniResultsTableToolbar.css: -------------------------------------------------------------------------------- 1 | 2 | .MuiInput-underline:has(#miniToolbarSelectDisplay):before { 3 | border-bottom: none; 4 | } 5 | 6 | .MuiInput-underline:has(#miniToolbarSelectDisplay):after { 7 | border-bottom: none; 8 | } -------------------------------------------------------------------------------- /ui/src/tools/toolCypherDebug/validation/validateCypher.js: -------------------------------------------------------------------------------- 1 | 2 | export const ValidationTerms = { 3 | Error: 'Error', 4 | Pass: 'Pass', 5 | NoData: 'No Data' 6 | } 7 | 8 | export const sleep = async (howLong) => { 9 | return await new Promise((resolve, reject) => { 10 | setTimeout(() => { 11 | resolve(); 12 | }, howLong) 13 | }) 14 | } 15 | 16 | export const runValidation = async (debugSteps, cypherParameters, functions) => { 17 | 18 | let { 19 | executeCypherAsPromise, 20 | getDebugCypherToRunFunction, 21 | checkIfUserHasCancelledFunction, 22 | reportStatusFunction 23 | } = functions; 24 | 25 | let options = { dontStringify: true }; 26 | 27 | while (debugSteps.canStepForward() && !checkIfUserHasCancelledFunction()) { 28 | // console.log('userHasCancelled: ', checkIfUserHasCancelledFunction()); 29 | debugSteps.internalStepForward(); 30 | let { cypherToRun, activeStep, activeInternalStep } = getDebugCypherToRunFunction(debugSteps, { limitOverride: 1 }); 31 | if (cypherToRun) { 32 | try { 33 | let results = await executeCypherAsPromise(cypherToRun, cypherParameters, null, options); 34 | let { headers, rows, isError } = results; 35 | let errorMessage = ''; 36 | if (isError) { 37 | errorMessage = rows[0][headers[0]]; 38 | } 39 | 40 | await reportStatusFunction({ 41 | cypher: cypherToRun, 42 | activeStep, 43 | activeInternalStep, 44 | rows, 45 | headers, 46 | isError, 47 | errorMessage 48 | }); 49 | 50 | } catch (e) { 51 | await reportStatusFunction({ 52 | cypher: cypherToRun, 53 | activeStep, 54 | activeInternalStep, 55 | rows: [], 56 | headers: [], 57 | isError: true, 58 | errorMessage: `${e}` 59 | }); 60 | } 61 | } 62 | // await sleep(5000); 63 | await sleep(10); 64 | } 65 | // console.log('userHasCancelled (2): ', checkIfUserHasCancelledFunction()); 66 | } -------------------------------------------------------------------------------- /ui/src/tools/toolCypherSuite/components/dataProvider/cypherBlock.js: -------------------------------------------------------------------------------- 1 | 2 | export default class CypherBlock { 3 | constructor (properties) { 4 | properties = properties || {}; 5 | 6 | var { 7 | key, 8 | title, 9 | expanded, 10 | selected, 11 | showToggleTool, 12 | scrollIntoView, 13 | ref, 14 | blockElement, 15 | graphNode, 16 | dataProvider 17 | } = properties; 18 | 19 | this.key = key; 20 | this.title = title; 21 | this.expanded = (expanded) ? true : false; 22 | this.selected = (selected) ? true : false; 23 | this.showToggleTool = (showToggleTool) ? true : false; 24 | this.scrollIntoView = (scrollIntoView) ? true : false; 25 | this.ref = ref; 26 | this.blockElement = blockElement; 27 | this.graphNode = graphNode; 28 | this.dataProvider = dataProvider; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ui/src/tools/toolCypherSuite/components/exportResults.js: -------------------------------------------------------------------------------- 1 | import { getCypherQueryInfo } from "../../common/cypher/processCypher"; 2 | import { runNeo4jToBigQuery } from "../../../persistence/graphql/GraphQLDataTransfer"; 3 | 4 | export const getExportPayload = ({cypherQuery, neoConnectionInfo, bigQueryExportInfo}) => { 5 | /* 6 | { 7 | "neoConnectionInfo": { 8 | ... 9 | }, 10 | "queryInfo": { 11 | "cypher": "MATCH (n) RETURN n.prop1 as prop1, n.prop2 as prop2", 12 | "outputFields": [ 13 | { 14 | name: "prop1", 15 | type: "String" 16 | }, 17 | { 18 | name: "prop2", 19 | type: "String" 20 | } 21 | ] 22 | }, 23 | "bigQueryOutput": { 24 | "projectId": "neo4j-solutions-workbench", 25 | "datasetId": "Paysim", 26 | "tableName": "cypherOutput" 27 | } 28 | } 29 | */ 30 | 31 | const queryInfo = getCypherQueryInfo(cypherQuery); 32 | if (!queryInfo) { 33 | return null; 34 | }; 35 | 36 | var { outputFields, cypher } = queryInfo; 37 | cypher = cypher || ''; 38 | // replace embedded newlines in cypher with spaces 39 | cypher = cypher.replace(/\n/g, ' '); 40 | 41 | var exportInfo = { 42 | neoConnectionInfo, 43 | queryInfo: { 44 | cypher, 45 | outputFields 46 | }, 47 | bigQueryOutput: bigQueryExportInfo 48 | } 49 | return exportInfo; 50 | } 51 | 52 | export const exportResults = ({cypherQuery, neoConnectionInfo, bigQueryExportInfo}, callback) => { 53 | const exportInfo = getExportPayload({cypherQuery, neoConnectionInfo, bigQueryExportInfo}); 54 | const exportInfoJson = JSON.stringify(exportInfo); 55 | const serviceID = "neo4j-to-bigquery" 56 | //console.log('exportInfo: ', exportInfo); 57 | if (exportInfo) { 58 | runNeo4jToBigQuery(serviceID, exportInfoJson, (response) => { 59 | //console.log('Run export job response: ', response); 60 | if (callback) { 61 | callback(response); 62 | } 63 | }); 64 | } 65 | } -------------------------------------------------------------------------------- /ui/src/tools/toolDashboard/components/dataProvider/dashboardCard.js: -------------------------------------------------------------------------------- 1 | 2 | export default class DashboardCard { 3 | constructor (properties) { 4 | properties = properties || {}; 5 | 6 | var { 7 | key, 8 | title, 9 | expanded, 10 | selected, 11 | showToggleTool, 12 | scrollIntoView, 13 | ref, 14 | blockElement, 15 | graphNode, 16 | dataProvider 17 | } = properties; 18 | 19 | this.key = key; 20 | this.title = title; 21 | this.expanded = (expanded) ? true : false; 22 | this.selected = (selected) ? true : false; 23 | this.showToggleTool = (showToggleTool) ? true : false; 24 | this.scrollIntoView = (scrollIntoView) ? true : false; 25 | this.ref = ref; 26 | this.blockElement = blockElement; 27 | this.graphNode = graphNode; 28 | this.dataProvider = dataProvider; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ui/src/tools/toolDashboard/components/dataProvider/dashboardCardDataProvider.js: -------------------------------------------------------------------------------- 1 | import { Node } from 'slate'; 2 | import DataTypes from '../../../../dataModel/DataTypes'; 3 | 4 | export class DashboardCardDataProvider { 5 | 6 | constructor (properties) { 7 | properties = (properties) ? properties : {}; 8 | 9 | var { 10 | dashboardCardTitle, 11 | dashboardCardBlockDataProvider, 12 | dashboardCardBlockKey, 13 | dashboardCardBuilder, 14 | graphNode, 15 | } = properties; 16 | 17 | this.graphNode = graphNode; 18 | this.dashboardCardBlockDataProvider = dashboardCardBlockDataProvider; 19 | this.dashboardCardBlockKey = dashboardCardBlockKey; 20 | this.dashboardCardBuilder = dashboardCardBuilder; 21 | this.dashboardCardTitle = (dashboardCardTitle) ? dashboardCardTitle : 'Dashboard Card'; 22 | 23 | this.loadDataIfPresent(); 24 | } 25 | 26 | data = () => this.dashboardCardBlockDataProvider.data(); 27 | 28 | getDataModel = () => this.dashboardCardBlockDataProvider.getDataModel(); 29 | 30 | setTitle = (dashboardCardTitle, ignoreSave) => { 31 | this.dashboardCardTitle = dashboardCardTitle; 32 | if (!ignoreSave) { 33 | this.graphNode.addOrUpdateProperty("dashboardCardTitle", dashboardCardTitle, DataTypes.String); 34 | } 35 | } 36 | 37 | getTitle = () => this.dashboardCardTitle; 38 | 39 | loadDataIfPresent = () => { 40 | const title = this.graphNode.getPropertyValueByKey("dashboardCardTitle", "Dashboard Card"); 41 | this.setTitle(title); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /ui/src/tools/toolDataMapping/dataProvider/updateTypes.js: -------------------------------------------------------------------------------- 1 | 2 | export const UpdateType = { 3 | SaveObjectReloaded: "SaveObjectReloaded", 4 | 5 | NodePatternAdded: "NodePatternAdded", 6 | NodePatternRemoved: "NodePatternRemoved", 7 | NodePatternUpdateNodeLabels: "NodePatternUpdateNodeLabels", 8 | NodePatternUpdateVariable: "NodePatternUpdateVariable", 9 | 10 | RelationshipPatternAdded: "RelationshipPatternAdded", 11 | RelationshipPatternRemoved: "RelationshipPatternRemoved", 12 | RelationshipPatternUpdateTypes: "RelationshipPatternUpdateTypes", 13 | RelationshipPatternUpdateVariable: "RelationshipPatternUpdateVariable", 14 | 15 | ReverseRelationshipPattern: "ReverseRelationshipPattern" 16 | } 17 | 18 | export default UpdateType; -------------------------------------------------------------------------------- /ui/src/tools/toolDatabases/components/DatabaseSharing.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { 3 | Button, 4 | Dialog, DialogTitle, DialogActions, DialogContent, FormControlLabel, 5 | Switch, Tab, Tabs, TextField 6 | } from '@material-ui/core'; 7 | 8 | import MultiTextField from '../../../components/common/MultiTextField'; 9 | import { StyledButton, TabPanel } from '../../../components/common/Components'; 10 | import { USER_ROLE } from '../../../common/Constants'; 11 | 12 | import DatabaseUserRoles from './DatabaseUserRoles'; 13 | 14 | export default class DatabaseSharing extends Component { 15 | 16 | constructor (props) { 17 | super(props); 18 | } 19 | 20 | isCreator = (currentUser) => { 21 | return (currentUser) ? (currentUser.role === USER_ROLE.CREATOR) : false; 22 | } 23 | 24 | isOwner = (currentUser) => { 25 | return (currentUser) ? (currentUser.role === USER_ROLE.OWNER) : false; 26 | } 27 | 28 | render () { 29 | var { open, onClose, maxWidth, save, currentUser, 30 | userRoles, upsertUser, removeUser } = this.props; 31 | 32 | const isCreator = this.isCreator(currentUser); 33 | const isOwner = this.isOwner(currentUser); 34 | 35 | return ( 36 | 37 | Sharing 38 | 39 |
40 | 42 |
43 |
44 | 45 | {(isCreator || isOwner) && 46 | 47 | Save 48 | 49 | } 50 | 51 | Cancel 52 | 53 | 54 |
55 | ) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ui/src/tools/toolDatabases/keyMakerComponents/Button.js: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | const Button = styled.button` 4 | font-size: 1em; 5 | font-weight: 500; 6 | padding-top: ${props => (props.thin ? "0.4em" : "0.7em")}; 7 | padding-bottom: ${props => (props.thin ? "0.4em" : "0.7em")}; 8 | padding-left: ${props => (props.narrow ? "0.8em" : "1.5em")}; 9 | padding-right: ${props => (props.narrow ? "0.8em" : "1.5em")}; 10 | width: ${props => (props.fluid ? "100%" : "auto")}; 11 | border-radius: ${props => (props.rounded ? "20px" : "3px")}; 12 | font-family: Lato, "Helvetica Neue", Arial, Helvetica, sans-serif; 13 | box-shadow: ${props => 14 | props.noShadow 15 | ? "none" 16 | : "0 3px 6px 0 rgba(0, 0, 0, 0.05), 0 6px 20px 0 rgba(0, 0, 0, 0.025)"}; 17 | border: none; 18 | outline: none; 19 | font-weight: bold; 20 | text-align: center; 21 | transition-duration: 0.15s; 22 | transition-timing-function: ease-out; 23 | 24 | &:hover { 25 | cursor: pointer; 26 | box-shadow: ${props => 27 | props.darkenOnHover ? "inset 0 0 0 1000px rgba(0, 0, 0, 0.025)" : "none"}; 28 | opacity: ${props => (props.lightenOnHover ? "0.75" : "1")}; 29 | transition-duration: 0.15s; 30 | transition-timing-function: ease-out; 31 | } 32 | 33 | &:active { 34 | outline: none; 35 | transition-duration: 0.15s; 36 | transition-timing-function: ease-out; 37 | } 38 | `; 39 | 40 | export default Button; 41 | -------------------------------------------------------------------------------- /ui/src/tools/toolDatabases/keyMakerComponents/Card.js: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import PropTypes from "prop-types"; 3 | 4 | const Card = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: space-between; 8 | width: ${props => (props.width ? props.width : "auto")}; 9 | height: ${props => (props.height ? props.height : "auto")}; 10 | box-shadow: ${props => 11 | props.noShadow 12 | ? "none" 13 | : "0 3px 6px 0 rgba(0, 0, 0, 0.05), 0 6px 20px 0 rgba(0, 0, 0, 0.025)"}; 14 | border-radius: 3px; 15 | background: white; 16 | `; 17 | 18 | Card.propTypes = { 19 | height: PropTypes.string, 20 | width: PropTypes.string 21 | }; 22 | 23 | export default Card; 24 | -------------------------------------------------------------------------------- /ui/src/tools/toolDatabases/keyMakerComponents/FormModal.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Modal from '@material-ui/core/Modal'; 3 | import "../KeyMakerApp.css"; 4 | import {Button} from "@material-ui/core"; 5 | 6 | import {makeStyles} from "@material-ui/core/styles"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | modal: { 10 | display: 'flex', 11 | alignItems: 'center', 12 | justifyContent: 'center', 13 | flexDirection: 'column' 14 | }, 15 | paper: { 16 | backgroundColor: theme.palette.background.paper, 17 | border: '2px solid #000', 18 | boxShadow: theme.shadows[5], 19 | padding: theme.spacing(2, 4, 3), 20 | }, 21 | buttonRow: { 22 | display: 'flex', 23 | justifyContent: 'space-around', 24 | marginTop: 10 25 | }, 26 | header: { 27 | display: 'flex', 28 | justifyContent: 'center', 29 | alignItems: 'center', 30 | color: 'red' 31 | }, 32 | controls: { 33 | display: 'flex', 34 | marginTop: 20, 35 | justifyContent: "space-between" 36 | } 37 | })); 38 | 39 | const FormModal = ({ 40 | isOpen, 41 | onClose, 42 | onSubmit, 43 | buttonName, 44 | children, 45 | }) => { 46 | const classes = useStyles(); 47 | return( 48 | 54 |
57 | {children} 58 |
61 | 67 | 74 |
75 |
76 |
77 | ); 78 | } 79 | 80 | export default FormModal; 81 | -------------------------------------------------------------------------------- /ui/src/tools/toolDatabases/keyMakerComponents/MessageModal.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Modal from '@material-ui/core/Modal'; 3 | import "../KeyMakerApp.css"; 4 | import {Typography} from "@material-ui/core"; 5 | import {makeStyles} from "@material-ui/core/styles"; 6 | 7 | const useStyles = makeStyles((theme) => ({ 8 | modal: { 9 | display: 'flex', 10 | alignItems: 'center', 11 | justifyContent: 'center', 12 | flexDirection: 'column' 13 | }, 14 | paper: { 15 | backgroundColor: theme.palette.background.paper, 16 | border: '2px solid #000', 17 | boxShadow: theme.shadows[5], 18 | padding: theme.spacing(2, 4, 3) 19 | }, 20 | buttonRow: { 21 | display: 'flex', 22 | justifyContent: 'space-around', 23 | marginTop: 10 24 | }, 25 | header: { 26 | display: 'flex', 27 | justifyContent: 'center', 28 | alignItems: 'center', 29 | color: 'red' 30 | } 31 | })); 32 | 33 | const MessageModal = ({ isOpen, onClose, title, message }) => { 34 | const classes = useStyles(); 35 | return ( 36 | 42 |
43 | 47 | {title} 48 | 49 | {message} 50 |
51 |
52 | ); 53 | 54 | } 55 | 56 | export default MessageModal; 57 | -------------------------------------------------------------------------------- /ui/src/tools/toolModel/components/properties/PropertyDefinition.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render, unmountComponentAtNode } from "react-dom"; 3 | import { act } from "react-dom/test-utils"; 4 | 5 | import DataModel from '../../../../dataModel/dataModel'; 6 | import { setLicensedFeatures, FEATURES } from '../../../../common/LicensedFeatures'; 7 | 8 | import { PropertyDefinition } from './PropertyDefinition'; 9 | 10 | let container = null; 11 | beforeEach(() => { 12 | // setup a DOM element as a render target 13 | setLicensedFeatures([FEATURES.MODEL.PropertyConstraints]) 14 | container = document.createElement("div"); 15 | document.body.appendChild(container); 16 | }); 17 | 18 | afterEach(() => { 19 | // cleanup on exiting 20 | unmountComponentAtNode(container); 21 | container.remove(); 22 | container = null; 23 | }); 24 | 25 | it("renders property definition", () => { 26 | var dataModel = DataModel(); 27 | act(() => { 28 | var propertyDefinition = { 29 | key: '123', 30 | name: 'myProperty', 31 | datatype: dataModel.DataTypes.String, 32 | referenceData: 'myRefData', 33 | isPartOfKey: true 34 | } 35 | render( {}} 39 | removePropertyDefinition={() => {}}/>, container); 40 | }); 41 | expect(container.querySelector("#name").value).toBe("myProperty"); 42 | expect(container.querySelector("input[name='datatype']").value).toBe(dataModel.DataTypes.String); 43 | expect(container.querySelector("#referenceData").value).toBe("myRefData"); 44 | expect(container.querySelector("input[value='isPartOfKey']").checked).toBe(true); 45 | }); 46 | -------------------------------------------------------------------------------- /ui/src/tools/toolModel/components/properties/PropertyDefinitions.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render, unmountComponentAtNode } from "react-dom"; 3 | import { act } from "react-dom/test-utils"; 4 | 5 | import DataModel from '../../../../dataModel/dataModel'; 6 | import { setLicensedFeatures, FEATURES } from '../../../../common/LicensedFeatures'; 7 | 8 | import PropertyDefinitions from './PropertyDefinitions'; 9 | 10 | let container = null; 11 | beforeEach(() => { 12 | setLicensedFeatures([FEATURES.MODEL.PropertyConstraints]) 13 | // setup a DOM element as a render target 14 | container = document.createElement("div"); 15 | document.body.appendChild(container); 16 | }); 17 | 18 | afterEach(() => { 19 | // cleanup on exiting 20 | unmountComponentAtNode(container); 21 | container.remove(); 22 | container = null; 23 | }); 24 | 25 | it("renders nodeLabel properties", () => { 26 | var dataModel = DataModel(); 27 | var propertyDefinitionsRef = React.createRef(); 28 | var nodeLabel = new dataModel.NodeLabel({ 29 | label: 'Test' 30 | }); 31 | dataModel.addNodeLabel(nodeLabel); 32 | var propertyMap = { 33 | key: 'propKey', 34 | name: 'myProperty', 35 | datatype: dataModel.DataTypes.String, 36 | referenceData: 'myRefData' 37 | } 38 | nodeLabel.addOrUpdateProperty (propertyMap, { isPartOfKey: true }); 39 | //console.log(nodeLabel); 40 | 41 | act(() => { 42 | var myNode = ; 43 | render(myNode, container); 44 | propertyDefinitionsRef.current.setPropertyContainer(nodeLabel); 45 | }); 46 | expect(container.querySelector("#name").value).toBe("myProperty"); 47 | expect(container.querySelector("input[name='datatype']").value).toBe(dataModel.DataTypes.String); 48 | expect(container.querySelector("#referenceData").value).toBe("myRefData"); 49 | expect(container.querySelector("input[value='isPartOfKey']").checked).toBe(true); 50 | }); 51 | -------------------------------------------------------------------------------- /ui/src/tools/toolModel/importexport/convert.js: -------------------------------------------------------------------------------- 1 | 2 | import uuidv4 from 'uuid/v4'; 3 | import { VERSION } from '../../../version'; 4 | 5 | export function convertMetadataToCurrentVersion (metadata) { 6 | // right now, the only change from metadata which has no version timestamp, to latest version 7 | var returnMetadata = { ...metadata }; 8 | if (!returnMetadata.cypherWorkbenchVersion) { 9 | returnMetadata.cypherWorkbenchVersion = VERSION; 10 | ensureTagsHaveKeys(returnMetadata); 11 | } 12 | return returnMetadata; 13 | } 14 | 15 | function ensureTagsHaveKeys (metadata) { 16 | if (metadata && metadata.tags && metadata.tags.length > 0) { 17 | // assign a key to each tag that has no key 18 | metadata.tags 19 | .filter(tag => !tag.key) 20 | .map(tag => tag.key = uuidv4()); 21 | } 22 | } -------------------------------------------------------------------------------- /ui/src/tools/toolModel/importexport/importCypherStatements.js: -------------------------------------------------------------------------------- 1 | 2 | export const DbSchema = ` 3 | CALL db.schema.visualization() YIELD nodes, relationships 4 | UNWIND nodes as nodeType 5 | WITH apoc.convert.toMap(nodeType) as nodeType, relationships 6 | UNWIND nodeType.constraints as constraint 7 | 8 | WITH nodeType, relationships, constraint, CASE 9 | WHEN constraint CONTAINS 'IS UNIQUE' THEN { type: 'isUnique', replaceTokens: ['CONSTRAINT ON ( ',' ) ASSERT ',' IS UNIQUE'] } 10 | WHEN constraint CONTAINS 'IS NODE KEY' THEN { type: 'isNodeKey', replaceTokens: ['CONSTRAINT ON ( ',' ) ASSERT (',',',') IS NODE KEY'] } 11 | WHEN constraint CONTAINS 'ASSERT exists' THEN { type: 'assertExists', replaceTokens: ['CONSTRAINT ON ( ',' ) ASSERT exists(', ')'] } 12 | END as constraintParseInfo 13 | 14 | WITH nodeType, relationships, constraintParseInfo, 15 | reduce(s = constraint, phrase IN constraintParseInfo.replaceTokens | replace(s, phrase, ' ')) as constraintParts 16 | WITH nodeType, relationships, constraintParseInfo, split(constraintParts, ' ') as constraintTokens 17 | WITH nodeType, relationships, constraintParseInfo, tail([x IN constraintTokens WHERE x <> '']) as constraintTokens 18 | WITH nodeType, relationships, constraintParseInfo, extract(x IN constraintTokens | split(x, '.')[1]) as propertyName 19 | WITH relationships, nodeType.name as name, collect({ type: constraintParseInfo.type, properties: propertyName}) as constraints 20 | WITH relationships, collect({ name: name, constraints: constraints }) as nodes 21 | UNWIND relationships as relationship 22 | WITH nodes, { 23 | startNode: apoc.convert.toMap(startNode(relationship)).name, 24 | relationshipType: type(relationship), 25 | endNode: apoc.convert.toMap(endNode(relationship)).name 26 | } as relationshipMap 27 | WITH nodes, collect(relationshipMap) as relationships 28 | RETURN { nodes: nodes, relationships: relationships } as dataModel 29 | `; 30 | -------------------------------------------------------------------------------- /ui/src/tools/toolScenarios/components/dataProvider/scenarioBlock.js: -------------------------------------------------------------------------------- 1 | 2 | export default class ScenarioBlock { 3 | constructor (properties) { 4 | properties = properties || {}; 5 | 6 | var { 7 | key, 8 | title, 9 | expanded, 10 | selected, 11 | showToggleTool, 12 | scrollIntoView, 13 | ref, 14 | blockElement, 15 | graphNode, 16 | dataProvider 17 | } = properties; 18 | 19 | this.key = key; 20 | this.title = title; 21 | this.expanded = (expanded) ? true : false; 22 | this.selected = (selected) ? true : false; 23 | this.showToggleTool = (showToggleTool) ? true : false; 24 | this.scrollIntoView = (scrollIntoView) ? true : false; 25 | this.ref = ref; 26 | this.blockElement = blockElement; 27 | this.graphNode = graphNode; 28 | this.dataProvider = dataProvider; 29 | } 30 | 31 | getWorkStatus = () => (this.dataProvider) ? this.dataProvider.getWorkStatus() : null 32 | getWorkMessage = () => (this.dataProvider) ? this.dataProvider.getWorkMessage() : null 33 | } 34 | -------------------------------------------------------------------------------- /ui/src/version.js: -------------------------------------------------------------------------------- 1 | 2 | // https://semver.org/ 3 | export const VERSION = '1.8.0'; 4 | -------------------------------------------------------------------------------- /ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } --------------------------------------------------------------------------------