@elyx-code/editor-ui 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +109 -0
- package/src/App.tsx +31 -0
- package/src/Router.tsx +115 -0
- package/src/__mocks__/defaultModuleMock.ts +1 -0
- package/src/__mocks__/fileMock.ts +1 -0
- package/src/__mocks__/styleMock.ts +1 -0
- package/src/assets/Clock-11.1s-18px.svg +16 -0
- package/src/assets/Clock-11.1s-28px.svg +16 -0
- package/src/assets/authentication.svg +1 -0
- package/src/assets/canvas-backdrop-0.png +0 -0
- package/src/assets/canvas-backdrop-1.png +0 -0
- package/src/assets/canvas-backdrop-2.png +0 -0
- package/src/assets/canvas-backdrop-3.png +0 -0
- package/src/assets/canvas-backdrop-4.png +0 -0
- package/src/assets/canvas-backdrop-5.png +0 -0
- package/src/assets/canvas-backdrop.png +0 -0
- package/src/assets/checkmark-animation.gif +0 -0
- package/src/assets/checkmark-animation.mp4 +0 -0
- package/src/assets/code-formatting/format-black.svg +6 -0
- package/src/assets/code-formatting/format-dark-grey.svg +6 -0
- package/src/assets/code-formatting/format-light-grey.svg +6 -0
- package/src/assets/code-formatting/format-white.svg +6 -0
- package/src/assets/code-formatting/inline-black.svg +5 -0
- package/src/assets/code-formatting/inline-dark-grey.svg +5 -0
- package/src/assets/code-formatting/inline-light-grey.svg +5 -0
- package/src/assets/code-formatting/inline-white.svg +5 -0
- package/src/assets/contained-logo-full-word.png +0 -0
- package/src/assets/cron-job-color.png +0 -0
- package/src/assets/cron-job.png +0 -0
- package/src/assets/database-table-color.png +0 -0
- package/src/assets/database-table.png +0 -0
- package/src/assets/datatype-icons/black/any.svg +1 -0
- package/src/assets/datatype-icons/black/binary.svg +1 -0
- package/src/assets/datatype-icons/black/boolean.svg +3 -0
- package/src/assets/datatype-icons/black/date-time.svg +3 -0
- package/src/assets/datatype-icons/black/definition-entity.svg +6 -0
- package/src/assets/datatype-icons/black/key-file.svg +1 -0
- package/src/assets/datatype-icons/black/list.svg +3 -0
- package/src/assets/datatype-icons/black/null.svg +3 -0
- package/src/assets/datatype-icons/black/number.svg +13 -0
- package/src/assets/datatype-icons/black/project.svg +12 -0
- package/src/assets/datatype-icons/black/sql-program.svg +2 -0
- package/src/assets/datatype-icons/black/text.svg +3 -0
- package/src/assets/datatype-icons/black/unknown.svg +3 -0
- package/src/assets/datatype-icons/black/uuid.svg +4 -0
- package/src/assets/datatype-icons/black/void.svg +1 -0
- package/src/assets/datatype-icons/dark-grey/any.svg +1 -0
- package/src/assets/datatype-icons/dark-grey/boolean.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/date-time.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/definition-entity.svg +6 -0
- package/src/assets/datatype-icons/dark-grey/list.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/null.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/number.svg +13 -0
- package/src/assets/datatype-icons/dark-grey/project.svg +12 -0
- package/src/assets/datatype-icons/dark-grey/sql-program.svg +2 -0
- package/src/assets/datatype-icons/dark-grey/text.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/unknown.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/uuid.svg +4 -0
- package/src/assets/datatype-icons/dark-grey/void.svg +1 -0
- package/src/assets/datatype-icons/light-grey/any.svg +1 -0
- package/src/assets/datatype-icons/light-grey/boolean.svg +3 -0
- package/src/assets/datatype-icons/light-grey/date-time.svg +3 -0
- package/src/assets/datatype-icons/light-grey/definition-entity.svg +6 -0
- package/src/assets/datatype-icons/light-grey/list.svg +3 -0
- package/src/assets/datatype-icons/light-grey/null.svg +3 -0
- package/src/assets/datatype-icons/light-grey/number.svg +13 -0
- package/src/assets/datatype-icons/light-grey/project.svg +12 -0
- package/src/assets/datatype-icons/light-grey/sql-program.svg +2 -0
- package/src/assets/datatype-icons/light-grey/text.svg +3 -0
- package/src/assets/datatype-icons/light-grey/unknown.svg +3 -0
- package/src/assets/datatype-icons/light-grey/uuid.svg +4 -0
- package/src/assets/datatype-icons/light-grey/void.svg +1 -0
- package/src/assets/edit.png +0 -0
- package/src/assets/execution.svg +13 -0
- package/src/assets/favicon.svg +14 -0
- package/src/assets/file-search.svg +1 -0
- package/src/assets/http-endpoint.png +0 -0
- package/src/assets/image-input-placeholder.png +0 -0
- package/src/assets/logo-full-word-white.png +0 -0
- package/src/assets/logo-full-word.png +0 -0
- package/src/assets/password.svg +85 -0
- package/src/assets/pencil.png +0 -0
- package/src/assets/publish-project-rich-icon-2.svg +1 -0
- package/src/assets/publish-project-rich-icon.svg +1 -0
- package/src/assets/relational-database.png +0 -0
- package/src/assets/resources.svg +3 -0
- package/src/assets/resume-icon-14px.png +0 -0
- package/src/assets/server.png +0 -0
- package/src/assets/small-status/checkmark.svg +4 -0
- package/src/assets/small-status/error.svg +4 -0
- package/src/assets/small-status/loading.svg +4 -0
- package/src/assets/small-status/skipped.svg +11 -0
- package/src/assets/sql-connection-config.svg +1 -0
- package/src/assets/sql-row-transformer.svg +1 -0
- package/src/assets/ssl-certificate-config.svg +1 -0
- package/src/assets/sync.svg +1 -0
- package/src/assets/testing-logic-icon.svg +1 -0
- package/src/assets/versions.svg +25 -0
- package/src/assets/visual-programming-icon.svg +1 -0
- package/src/assets/warning-sign-24px.png +0 -0
- package/src/auth/index.ts +318 -0
- package/src/components/DialogLoader.tsx +94 -0
- package/src/components/EntityDialogHeader.tsx +110 -0
- package/src/components/EntityDialogSectionHeader.tsx +214 -0
- package/src/components/GalleryAddExternalIntegrationInfoDialog.tsx +87 -0
- package/src/components/GenerateProjectStartingLogicPromptDialog.tsx +281 -0
- package/src/components/LegacyRouteRedirector.tsx +55 -0
- package/src/components/ProPlanChip.tsx +23 -0
- package/src/components/ReportBugDialog.tsx +412 -0
- package/src/components/RequestIntegrationAccessDialog.tsx +261 -0
- package/src/components/UseTemplateProjectDialog.tsx +193 -0
- package/src/components/WorkspaceLayout.tsx +152 -0
- package/src/components/animated-svg/AnimatedCheckmark.tsx +41 -0
- package/src/components/animated-svg/AnimatedCrossmark.tsx +51 -0
- package/src/components/animated-svg/AnimatedEmailSending.tsx +38 -0
- package/src/components/animated-svg/AnimatedLoading.tsx +72 -0
- package/src/components/animated-svg/animated-svg.css +239 -0
- package/src/components/canvas/Canvas.tsx +16 -0
- package/src/components/canvas/CreateEntityMenu.tsx +2020 -0
- package/src/components/canvas/canvas.css +10 -0
- package/src/components/canvas/create-entity-menu.css +579 -0
- package/src/components/canvas-search/CanvasSearch.tsx +501 -0
- package/src/components/canvas-search/canvas-search.css +126 -0
- package/src/components/canvas-settings-menu/CanvasSettingsMenuButton.tsx +515 -0
- package/src/components/canvas-settings-menu/canvas-settings-menu.css +96 -0
- package/src/components/circular-image-upload/CircularImageUpload.tsx +113 -0
- package/src/components/circular-image-upload/circular-image-upload.css +69 -0
- package/src/components/costs/CostsDialog.tsx +459 -0
- package/src/components/data-type/DataTypeBuilder.tsx +3127 -0
- package/src/components/data-type/data-type-builder.css +45 -0
- package/src/components/dialogs/BetaAcknowledgeDialog.tsx +43 -0
- package/src/components/dialogs/ComplexDataDialog.tsx +458 -0
- package/src/components/dialogs/CronBuilderDialog.tsx +2145 -0
- package/src/components/dialogs/ExternalIntegrationConnections.tsx +565 -0
- package/src/components/dialogs/JsonEditorDialog.tsx +1392 -0
- package/src/components/dialogs/StringEditorDialog.tsx +268 -0
- package/src/components/dialogs/argument-declaration/ArgumentDeclaration.tsx +1167 -0
- package/src/components/dialogs/argument-declaration/ArgumentDeclarationDialogContent.tsx +128 -0
- package/src/components/dialogs/beta-dialog.css +165 -0
- package/src/components/dialogs/condition/Condition.tsx +431 -0
- package/src/components/dialogs/condition/ConditionDialogContent.tsx +126 -0
- package/src/components/dialogs/definition-entity/DefinitionEntityDialogContent.tsx +973 -0
- package/src/components/dialogs/function-call/FunctionCall.tsx +442 -0
- package/src/components/dialogs/function-call/FunctionCallDialogContent.tsx +126 -0
- package/src/components/dialogs/function-declaration/FunctionDeclaration.tsx +926 -0
- package/src/components/dialogs/function-declaration/FunctionDeclarationDialogContent.tsx +124 -0
- package/src/components/dialogs/generating-project-starting-logic-overlay/GeneratingProjectStartingLogicOverlay.tsx +176 -0
- package/src/components/dialogs/generating-project-starting-logic-overlay/generating-project-starting-logic-overlay.css +13 -0
- package/src/components/dialogs/global-event/GlobalEvent.tsx +475 -0
- package/src/components/dialogs/global-event/GlobalEventDialogContent.tsx +126 -0
- package/src/components/dialogs/help/HelpDialog.tsx +217 -0
- package/src/components/dialogs/help/HelpDilalogHomeContent.tsx +178 -0
- package/src/components/dialogs/help/help-dialog.css +116 -0
- package/src/components/dialogs/help/help-icon/HelpIconButton.tsx +41 -0
- package/src/components/dialogs/help/help-icon/help-icon.css +9 -0
- package/src/components/dialogs/input-map/InputMap.tsx +635 -0
- package/src/components/dialogs/input-map/InputMapDialogContent.tsx +126 -0
- package/src/components/dialogs/json-editor-dialog.css +4 -0
- package/src/components/dialogs/loop/Loop.tsx +650 -0
- package/src/components/dialogs/loop/LoopDialogContent.tsx +122 -0
- package/src/components/dialogs/operation/Operation.tsx +440 -0
- package/src/components/dialogs/operation/OperationDialogContent.tsx +126 -0
- package/src/components/dialogs/output-map/OutputMap.tsx +536 -0
- package/src/components/dialogs/output-map/OutputMapDialogContent.tsx +126 -0
- package/src/components/dialogs/property/Property.tsx +1490 -0
- package/src/components/dialogs/property/PropertyDialogContent.tsx +106 -0
- package/src/components/dialogs/search-statement/ColumnSelector.tsx +334 -0
- package/src/components/dialogs/search-statement/ConditionBuilder.tsx +750 -0
- package/src/components/dialogs/search-statement/DataAggregationSection.tsx +621 -0
- package/src/components/dialogs/search-statement/DataSourceSelection.tsx +734 -0
- package/src/components/dialogs/search-statement/EntityMetadataSection.tsx +135 -0
- package/src/components/dialogs/search-statement/FilterConditionsSection.tsx +151 -0
- package/src/components/dialogs/search-statement/InlineInputMap.tsx +153 -0
- package/src/components/dialogs/search-statement/LiteralValue.tsx +616 -0
- package/src/components/dialogs/search-statement/MainSourceAndInputsSection.tsx +271 -0
- package/src/components/dialogs/search-statement/NestedSearchStatementBuilder.tsx +170 -0
- package/src/components/dialogs/search-statement/OutputFormatSection.tsx +1779 -0
- package/src/components/dialogs/search-statement/ResultsSection.tsx +344 -0
- package/src/components/dialogs/search-statement/SearchStatementBuilder.tsx +251 -0
- package/src/components/dialogs/search-statement/SearchStatementDialogContent.tsx +398 -0
- package/src/components/dialogs/search-statement/ValueSelector.tsx +766 -0
- package/src/components/dialogs/search-statement/search-statement-context.tsx +1630 -0
- package/src/components/dialogs/search-statement/search-statement-dialog.css +56 -0
- package/src/components/dialogs/search-statement/test.sql +111 -0
- package/src/components/dialogs/value-descriptor/ValueDescriptor.tsx +824 -0
- package/src/components/dialogs/value-descriptor/ValueDescriptorDialogContent.tsx +124 -0
- package/src/components/dialogs/variable-declaration/VariableDeclaration.tsx +836 -0
- package/src/components/dialogs/variable-declaration/VariableDeclarationDialogContent.tsx +106 -0
- package/src/components/dialogs/variable-instance/VariableInstance.tsx +443 -0
- package/src/components/dialogs/variable-instance/VariableInstanceDialogContent.tsx +124 -0
- package/src/components/draggable-entity-card/ArgumentDeclaration.tsx +736 -0
- package/src/components/draggable-entity-card/CollapseEntityButton.tsx +170 -0
- package/src/components/draggable-entity-card/ConditionCard.tsx +1062 -0
- package/src/components/draggable-entity-card/ConnectionDeleteButton.tsx +309 -0
- package/src/components/draggable-entity-card/DataTypeIcon.tsx +624 -0
- package/src/components/draggable-entity-card/DraggableEntityCard.tsx +617 -0
- package/src/components/draggable-entity-card/ErrorMapProperty.tsx +464 -0
- package/src/components/draggable-entity-card/EventCard.tsx +700 -0
- package/src/components/draggable-entity-card/ExecutionInProgressValue.tsx +327 -0
- package/src/components/draggable-entity-card/FunctionDeclarationCard.tsx +819 -0
- package/src/components/draggable-entity-card/InputMapProperty.tsx +1067 -0
- package/src/components/draggable-entity-card/InternalCall.tsx +978 -0
- package/src/components/draggable-entity-card/InternalCallExecutionNode.tsx +643 -0
- package/src/components/draggable-entity-card/LogicScopeCallerNode.tsx +262 -0
- package/src/components/draggable-entity-card/LoopCard.tsx +791 -0
- package/src/components/draggable-entity-card/MainValueInput.tsx +523 -0
- package/src/components/draggable-entity-card/MainValueOutput.tsx +458 -0
- package/src/components/draggable-entity-card/MethodDeclaration.tsx +1088 -0
- package/src/components/draggable-entity-card/NestedCondition.tsx +1025 -0
- package/src/components/draggable-entity-card/OutputMapProperty.tsx +843 -0
- package/src/components/draggable-entity-card/PassthroughEntityCard.tsx +1247 -0
- package/src/components/draggable-entity-card/ReturnedError.tsx +549 -0
- package/src/components/draggable-entity-card/SmallSuccessFailureNodes.tsx +523 -0
- package/src/components/draggable-entity-card/SuccessFailureNodes.tsx +509 -0
- package/src/components/draggable-entity-card/TestEntityButton.tsx +946 -0
- package/src/components/draggable-entity-card/TestMenu.tsx +523 -0
- package/src/components/draggable-entity-card/TestMenuValidationDropdown.tsx +84 -0
- package/src/components/draggable-entity-card/UnreachableMarker.tsx +114 -0
- package/src/components/draggable-entity-card/VariableCard.tsx +1577 -0
- package/src/components/draggable-entity-card/VariableScopeMarker.tsx +117 -0
- package/src/components/draggable-entity-card/collapse-entity-button.css +44 -0
- package/src/components/draggable-entity-card/definition-entity/DefinitionEntityCard.tsx +1181 -0
- package/src/components/draggable-entity-card/definition-entity/DefinitionEntityIcon.tsx +36 -0
- package/src/components/draggable-entity-card/definition-entity/DefinitionEntityProperty.tsx +478 -0
- package/src/components/draggable-entity-card/definition-entity/DynamicFooterActions.tsx +112 -0
- package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/ExportCredentialsFooterAction.tsx +461 -0
- package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/RestablishConnectionFooterAction.tsx +199 -0
- package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/restablish-connection-footer-action.css +85 -0
- package/src/components/draggable-entity-card/definition-entity/actions/google-drive/GoogleDriveFilePickerAPIFooterAction.tsx +277 -0
- package/src/components/draggable-entity-card/definition-entity/actions/google-drive/google-drive-file-picker-api-footer-action.css +107 -0
- package/src/components/draggable-entity-card/definition-entity/actions/persisted-entity/DatabaseFooterAction.tsx +452 -0
- package/src/components/draggable-entity-card/definition-entity/actions/persisted-entity/database-footer-action.css +86 -0
- package/src/components/draggable-entity-card/definition-entity/definition-entity-card.css +17 -0
- package/src/components/draggable-entity-card/draggable-entity-card.css +1140 -0
- package/src/components/draggable-entity-card/entity-locked-icon/EntityLockedIcon.tsx +133 -0
- package/src/components/draggable-entity-card/entity-locked-icon/entity-locked.css +8 -0
- package/src/components/draggable-entity-card/expand-properties-icon-button/ExpandPropertiesIconButton.tsx +84 -0
- package/src/components/draggable-entity-card/expand-properties-icon-button/expand-properties-icon-button.css +21 -0
- package/src/components/draggable-entity-card/implement-entity-icon/ImplementEntityIcon.tsx +74 -0
- package/src/components/draggable-entity-card/implement-entity-icon/implement-entity-icon.css +13 -0
- package/src/components/draggable-entity-card/logic-error/LogicErrorIconMenu.tsx +424 -0
- package/src/components/draggable-entity-card/logic-error/logic-error.css +23 -0
- package/src/components/draggable-entity-card/new-card-input-button/NewCardInputButton.tsx +193 -0
- package/src/components/draggable-entity-card/new-card-input-button/NewDynamicInputButton.tsx +214 -0
- package/src/components/draggable-entity-card/new-card-input-button/new-card-input-button.css +71 -0
- package/src/components/draggable-entity-card/new-card-output-button/NewCardOutputButton.tsx +192 -0
- package/src/components/draggable-entity-card/new-card-output-button/new-card-output-button.css +71 -0
- package/src/components/draggable-entity-card/termination-statement/TerminationStatementCard.tsx +1543 -0
- package/src/components/draggable-entity-card/termination-statement/termination-statement-card.css +17 -0
- package/src/components/draggable-entity-card/test-entity-button.css +55 -0
- package/src/components/draggable-entity-card/test-menu.css +181 -0
- package/src/components/draggable-entity-card/unreachable-marker.css +43 -0
- package/src/components/draggable-entity-card/variable-scope-marker.css +22 -0
- package/src/components/dynamic-value/DynamicValue.tsx +2395 -0
- package/src/components/dynamic-value/DynamicValueEntry.tsx +1957 -0
- package/src/components/dynamic-value/dynamic-value.css +230 -0
- package/src/components/editor/ElyxMonacoEditor.tsx +38 -0
- package/src/components/entity-error/EntityErrorListItem.tsx +47 -0
- package/src/components/entity-error/entity-error.css +198 -0
- package/src/components/entity-icon/EntityIcon.tsx +292 -0
- package/src/components/entity-icon/entity-icon.css +39 -0
- package/src/components/gallery-card/CreateNewProject.tsx +222 -0
- package/src/components/gallery-card/GalleryCard.tsx +171 -0
- package/src/components/gallery-card/MarketplaceCard.tsx +87 -0
- package/src/components/gallery-card/ProjectDuplicationCard.tsx +575 -0
- package/src/components/gallery-card/gallery-card.css +25 -0
- package/src/components/notifications/NotificationsIconButton.tsx +124 -0
- package/src/components/notifications/NotificationsPanel.tsx +385 -0
- package/src/components/notifications/notifications.css +189 -0
- package/src/components/online-users/LocalOnlineUsers.tsx +175 -0
- package/src/components/online-users/PageOnlineUsers.tsx +297 -0
- package/src/components/online-users/online-users.css +72 -0
- package/src/components/page-backdrop/PageBackdrop.tsx +8 -0
- package/src/components/page-backdrop/page-backdrop.css +7 -0
- package/src/components/project-configuration/DeleteProjectConfirmationDialog.tsx +134 -0
- package/src/components/project-configuration/ProjectConfigurationDialog.tsx +972 -0
- package/src/components/project-configuration/ProjectDataForm.tsx +121 -0
- package/src/components/project-configuration/UnpublishProjectConfirmationDialog.tsx +162 -0
- package/src/components/project-configuration/project-configuration-content.css +209 -0
- package/src/components/project-name/ProjectName.tsx +2025 -0
- package/src/components/project-name/project-name.css +599 -0
- package/src/components/publishing/Publication.tsx +133 -0
- package/src/components/publishing/history/PublicationHistoryContent.tsx +414 -0
- package/src/components/publishing/history/PublicationHistoryDialog.tsx +234 -0
- package/src/components/publishing/preview/PublicationPreviewDialog.tsx +1158 -0
- package/src/components/publishing/preview/PublishingPriceForecast.tsx +160 -0
- package/src/components/publishing/preview/PublishingResourcesDetails.tsx +91 -0
- package/src/components/publishing/publication-sequence/PublishingSequenceContent.tsx +375 -0
- package/src/components/publishing/publication-sequence/PublishingSequenceDialog.tsx +344 -0
- package/src/components/publishing/publishing-dialog.css +142 -0
- package/src/components/publishing/utils.ts +227 -0
- package/src/components/resources/ResourcesDialog.tsx +591 -0
- package/src/components/resources/UpgradeBanner.tsx +102 -0
- package/src/components/resources/codebase/CodebaseDetails.tsx +156 -0
- package/src/components/resources/cron-job/CronJobsList.tsx +532 -0
- package/src/components/resources/functions/FunctionsList.tsx +454 -0
- package/src/components/resources/http-api/HttpAPI.tsx +566 -0
- package/src/components/resources/http-api/HttpAPIClientModule.tsx +37 -0
- package/src/components/resources/logs/LogsViewer.tsx +768 -0
- package/src/components/resources/query.ts +74 -0
- package/src/components/resources/relational-database/DatabaseTable.tsx +905 -0
- package/src/components/resources/relational-database/RelationalDatabase.tsx +83 -0
- package/src/components/resources/relational-database/RelationalDatabaseSecrets.tsx +361 -0
- package/src/components/resources/resources-dialog.css +74 -0
- package/src/components/test-relational-database/DatabaseTable.tsx +913 -0
- package/src/components/test-relational-database/TestDatabaseDialogContent.tsx +670 -0
- package/src/components/test-relational-database/query.ts +74 -0
- package/src/components/toolbar/ToolBar.tsx +236 -0
- package/src/components/toolbar/toolbar.css +78 -0
- package/src/components/transaction-history/TransactionHistoryDialog.tsx +268 -0
- package/src/components/user/CurrentUserAvatar.tsx +65 -0
- package/src/components/user/UserChip.tsx +62 -0
- package/src/components/user/user.css +39 -0
- package/src/components/user-profile/ChangePasswordForm.tsx +67 -0
- package/src/components/user-profile/OwnUserProfileContent.tsx +665 -0
- package/src/components/user-profile/PublicUserProfileContent.tsx +99 -0
- package/src/components/user-profile/UserDataForm.tsx +75 -0
- package/src/components/user-profile/UserProfileDialog.tsx +110 -0
- package/src/components/user-profile/user-profile-content.css +25 -0
- package/src/config.ts +130 -0
- package/src/globals.d.ts +13 -0
- package/src/index.html +27 -0
- package/src/index.tsx +23 -0
- package/src/lib/badge/Badge.tsx +35 -0
- package/src/lib/badge/badge.css +32 -0
- package/src/lib/button/Button.tsx +129 -0
- package/src/lib/button/button.css +145 -0
- package/src/lib/canvas/canvas-undo-redo.ts +263 -0
- package/src/lib/canvas/defs.ts +170 -0
- package/src/lib/canvas/index.test.ts +189 -0
- package/src/lib/canvas/index.ts +6999 -0
- package/src/lib/canvas/utils.ts +59 -0
- package/src/lib/card/Card.tsx +62 -0
- package/src/lib/card/LoadingCard.tsx +82 -0
- package/src/lib/card/card.css +259 -0
- package/src/lib/chip/Chip.tsx +79 -0
- package/src/lib/chip/chip.css +0 -0
- package/src/lib/dialog/Dialog.tsx +122 -0
- package/src/lib/dialog/SmallDialog.tsx +61 -0
- package/src/lib/dialog/dialog.css +40 -0
- package/src/lib/display-data-structure/index.tsx +21 -0
- package/src/lib/dropdown/CanvasDropdownMenuCard.tsx +68 -0
- package/src/lib/dropdown/CanvasDropdownMenuCardOption.tsx +136 -0
- package/src/lib/dropdown/DropdownButton.tsx +104 -0
- package/src/lib/dropdown/DropdownMenuCard.tsx +324 -0
- package/src/lib/dropdown/DropdownMenuPopup.tsx +27 -0
- package/src/lib/dropdown/dropdown-button.css +76 -0
- package/src/lib/dropdown/dropdown-menu.css +151 -0
- package/src/lib/json-editor/RawJsonEditor.tsx +137 -0
- package/src/lib/json-editor/json-editor.css +35 -0
- package/src/lib/loader/Loader.tsx +120 -0
- package/src/lib/loader/loader.css +38 -0
- package/src/lib/pagination/Pagination.tsx +64 -0
- package/src/lib/popup/CanvasPopupBaseComponent.tsx +103 -0
- package/src/lib/popup/Popup.tsx +243 -0
- package/src/lib/popup/popup.css +16 -0
- package/src/lib/table/RowForm.tsx +301 -0
- package/src/lib/table/Table.tsx +1069 -0
- package/src/lib/table/table.css +249 -0
- package/src/lib/table/types.ts +108 -0
- package/src/lib/text-area/TextArea.tsx +183 -0
- package/src/lib/text-area/text-area.css +156 -0
- package/src/lib/text-field/TextField.tsx +218 -0
- package/src/lib/text-field/index.ts +8 -0
- package/src/lib/text-field/text-field.css +201 -0
- package/src/lib/tooltip/Tooltip.tsx +24 -0
- package/src/lib/tooltip/tooltip.css +17 -0
- package/src/localization/index.ts +47 -0
- package/src/main.css +343 -0
- package/src/pages/Auth.tsx +848 -0
- package/src/pages/Editor.tsx +883 -0
- package/src/pages/ErrorPage.tsx +179 -0
- package/src/pages/Gallery.tsx +1693 -0
- package/src/pages/NewPaymentMethodCallback.tsx +53 -0
- package/src/pages/NotFoundPage.tsx +126 -0
- package/src/pages/PricingPlans.tsx +155 -0
- package/src/pages/auth.css +304 -0
- package/src/pages/gallery.css +421 -0
- package/src/payments/index.ts +187 -0
- package/src/popup-notification/index.ts +90 -0
- package/src/services/database/index.ts +1 -0
- package/src/services/database/utils.ts +1301 -0
- package/src/services/editor/CanvasElement.tsx +2934 -0
- package/src/services/editor/CanvasElementConnectionDeleteButton.ts +204 -0
- package/src/services/editor/CanvasPopup.tsx +749 -0
- package/src/services/editor/EditorService.ts +8157 -0
- package/src/services/editor/area.ts +1312 -0
- package/src/services/editor/connections.ts +1019 -0
- package/src/services/editor/create/condition.ts +25 -0
- package/src/services/editor/create/definition-entity.ts +29 -0
- package/src/services/editor/create/function-call.ts +25 -0
- package/src/services/editor/create/global-event.ts +33 -0
- package/src/services/editor/create/loop.ts +25 -0
- package/src/services/editor/create/operation.ts +30 -0
- package/src/services/editor/create/utils.ts +140 -0
- package/src/services/editor/create/variable-declaration.ts +135 -0
- package/src/services/editor/create/variable-instance.ts +100 -0
- package/src/services/editor/editor-ui-extensions-context.ts +43 -0
- package/src/services/editor/entities-metadata.json +9310 -0
- package/src/services/editor/icons.ts +1093 -0
- package/src/services/editor/index.ts +1 -0
- package/src/services/editor/layout.ts +102 -0
- package/src/services/editor/modules/built-in-function-implementations/base.ts +14 -0
- package/src/services/editor/modules/built-in-function-implementations/create-persisted-entity/index.ts +56 -0
- package/src/services/editor/modules/built-in-function-implementations/delete-persisted-entity/index.ts +55 -0
- package/src/services/editor/modules/built-in-function-implementations/index.ts +4 -0
- package/src/services/editor/modules/built-in-function-implementations/update-persisted-entity/index.ts +56 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/get-files.ts +183 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/list-drives.ts +124 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/list-root-folders.ts +125 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/smart-fetch-document.ts +702 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/upload-document.ts +535 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-gemini/generate-content.ts +193 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-mail/get-emails.ts +586 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-mail/send-email.ts +386 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/index.ts +12 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/slack/channels.ts +240 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/slack/messages.ts +210 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/slack/replies.ts +200 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/slack/send-message.ts +177 -0
- package/src/services/editor/modules/operations-implementations/index.ts +1 -0
- package/src/services/editor/modules/search-node-implementation/index.ts +42 -0
- package/src/services/editor/modules/sql-migrations-generation.tsx +1054 -0
- package/src/services/editor/publication/publication.ts +578 -0
- package/src/services/editor/ui.ts +1348 -0
- package/src/services/editor/utils.ts +5868 -0
- package/src/services/editor/value-store.ts +619 -0
- package/src/services/execution/built-in-function-implementations.ts +422 -0
- package/src/services/execution/index.ts +4747 -0
- package/src/services/execution/logic.ts +121 -0
- package/src/services/execution/test-instance.tsx +2296 -0
- package/src/services/execution/utils.ts +33 -0
- package/src/services/execution/value-resolution.test.ts +424 -0
- package/src/services/execution/value-resolution.ts +4087 -0
- package/src/services/integrations/ExternalIntegrationsService.ts +439 -0
- package/src/services/integrations/api.ts +175 -0
- package/src/services/local-relational-database/idb_helper.ts +66 -0
- package/src/services/local-relational-database/index.ts +3308 -0
- package/src/services/local-relational-database/utils.ts +403 -0
- package/src/services/notifications/index.ts +525 -0
- package/src/services/user/index.ts +144 -0
- package/src/setupTests.ts +1 -0
- package/src/socket/socket.ts +248 -0
- package/src/socket/utils.ts +10 -0
- package/src/store/workspace.ts +12 -0
- package/src/theme.ts +19 -0
- package/src/utils/DOM.ts +39 -0
- package/src/utils/date.ts +169 -0
- package/src/utils/index.ts +158 -0
- package/src/utils/react.tsx +679 -0
- package/src/utils/testing.ts +103 -0
|
@@ -0,0 +1,1630 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
ReactNode,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState
|
|
8
|
+
} from 'react';
|
|
9
|
+
import {
|
|
10
|
+
EntityId,
|
|
11
|
+
EntityType,
|
|
12
|
+
ProjectState,
|
|
13
|
+
SearchState,
|
|
14
|
+
ChangeSet,
|
|
15
|
+
SQLAST as SQLASTLib,
|
|
16
|
+
searchStatementState as searchStatementStateLib,
|
|
17
|
+
SQLAST,
|
|
18
|
+
searchStatementUtils,
|
|
19
|
+
syncSearchEntityOutput,
|
|
20
|
+
searchStatementDefs,
|
|
21
|
+
StateMutationAction
|
|
22
|
+
} from '@elyx-code/project-logic-tree';
|
|
23
|
+
import { EditorService } from '../../../services/editor';
|
|
24
|
+
import { IDialogProps } from '../../../lib/dialog/Dialog';
|
|
25
|
+
import { useParams } from 'react-router-dom';
|
|
26
|
+
import { Database } from '../../../services/local-relational-database';
|
|
27
|
+
import { popupNotification } from '../../../popup-notification';
|
|
28
|
+
import { Logger } from '@elyx-code/common-ts-utils';
|
|
29
|
+
|
|
30
|
+
const { parse, cstVisitor } = SQLASTLib;
|
|
31
|
+
const { SearchStatementLiteralValue } = searchStatementStateLib;
|
|
32
|
+
|
|
33
|
+
const { SearchStatementNodeType, SearchLiteralValueType } = searchStatementDefs;
|
|
34
|
+
|
|
35
|
+
type AggregationStatement = searchStatementStateLib.AggregationStatement;
|
|
36
|
+
type AllColumnsSelector = searchStatementStateLib.AllColumnsSelector;
|
|
37
|
+
type FunctionCall = searchStatementStateLib.FunctionCall;
|
|
38
|
+
type ColumnRef = searchStatementStateLib.ColumnRef;
|
|
39
|
+
type DataSource = searchStatementStateLib.DataSource;
|
|
40
|
+
type SearchStatementLiteralValue =
|
|
41
|
+
searchStatementStateLib.SearchStatementLiteralValue;
|
|
42
|
+
type ValueRef = searchStatementStateLib.ValueRef;
|
|
43
|
+
type SearchStatementState = searchStatementStateLib.SearchStatementState;
|
|
44
|
+
type SortStatement = searchStatementStateLib.SortStatement;
|
|
45
|
+
type WhereStatement = searchStatementStateLib.WhereStatement;
|
|
46
|
+
type SearchLiteralValueType = searchStatementDefs.SearchLiteralValueType;
|
|
47
|
+
|
|
48
|
+
type SQLAST = SQLASTLib.Program;
|
|
49
|
+
type SqlAstNode = SQLASTLib.Node;
|
|
50
|
+
|
|
51
|
+
const testQuery = `-- Select specific columns from the Employee table
|
|
52
|
+
SELECT
|
|
53
|
+
e.identifier AS EmployeeID,
|
|
54
|
+
e.department,
|
|
55
|
+
e.startDate,
|
|
56
|
+
m.identifier AS ManagerID,
|
|
57
|
+
m.department AS ManagerDepartment,
|
|
58
|
+
m.startDate AS ManagerStartDate
|
|
59
|
+
FROM
|
|
60
|
+
Employee e
|
|
61
|
+
LEFT JOIN
|
|
62
|
+
Employee m ON e.manager = m.identifier
|
|
63
|
+
WHERE
|
|
64
|
+
-- Filter: Search for a specific employee by their UUID
|
|
65
|
+
e.identifier = 'your-employee-uuid'
|
|
66
|
+
-- Additional Filter: Employees that belong to a specific department
|
|
67
|
+
AND e.department = 'Sales'
|
|
68
|
+
-- Additional Filter: Employees who started after a certain date
|
|
69
|
+
AND e.startDate > '2022-01-01'
|
|
70
|
+
ORDER BY
|
|
71
|
+
-- Sorting: Sort employees by their start date, descending
|
|
72
|
+
e.startDate DESC,
|
|
73
|
+
-- Sorting: Sort by department alphabetically after start date
|
|
74
|
+
e.department ASC
|
|
75
|
+
LIMIT
|
|
76
|
+
-- Limit the results to 10 employees
|
|
77
|
+
10
|
|
78
|
+
OFFSET
|
|
79
|
+
-- Skip the first 2 employees
|
|
80
|
+
2;
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
const testQuery2 = `select * from Employee;`;
|
|
84
|
+
|
|
85
|
+
const testQuery3 = `-- Select specific columns from the Employee table
|
|
86
|
+
SELECT
|
|
87
|
+
Employee.identifier AS EmployeeID,
|
|
88
|
+
Employee.department,
|
|
89
|
+
Employee.startDate,
|
|
90
|
+
m.identifier AS ManagerID,
|
|
91
|
+
m.department AS ManagerDepartment,
|
|
92
|
+
m.startDate AS ManagerStartDate
|
|
93
|
+
FROM
|
|
94
|
+
Employee
|
|
95
|
+
LEFT JOIN
|
|
96
|
+
Employee m ON Employee.manager = m.identifier
|
|
97
|
+
WHERE
|
|
98
|
+
-- Filter: Search for a specific employee by their UUID
|
|
99
|
+
Employee.identifier = 'your-employee-uuid'
|
|
100
|
+
-- Additional Filter: Employees that belong to a specific department
|
|
101
|
+
AND Employee.department = 'Sales'
|
|
102
|
+
-- Additional Filter: Employees who started after a certain date
|
|
103
|
+
AND Employee.startDate > '2022-01-01'
|
|
104
|
+
ORDER BY
|
|
105
|
+
-- Sorting: Sort employees by their start date, descending
|
|
106
|
+
Employee.startDate DESC,
|
|
107
|
+
-- Sorting: Sort by department alphabetically after start date
|
|
108
|
+
Employee.department ASC
|
|
109
|
+
LIMIT
|
|
110
|
+
-- Limit the results to 10 employees
|
|
111
|
+
10
|
|
112
|
+
OFFSET
|
|
113
|
+
-- Skip the first 2 employees
|
|
114
|
+
2;
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
const testQuery4 = `SELECT *
|
|
118
|
+
from Employee
|
|
119
|
+
ORDER BY startDate desc, department asc
|
|
120
|
+
limit 10 offset 2`;
|
|
121
|
+
|
|
122
|
+
const testQuery5 = `SELECT
|
|
123
|
+
Employee.identifier AS EmployeeID
|
|
124
|
+
from Employee
|
|
125
|
+
ORDER BY startDate desc, department asc
|
|
126
|
+
limit 10 offset 2`;
|
|
127
|
+
|
|
128
|
+
const testQuery6 = `SELECT
|
|
129
|
+
Employee.identifier AS EmployeeID,
|
|
130
|
+
User.identifier AS UserID
|
|
131
|
+
from Employee
|
|
132
|
+
INNER JOIN User ON
|
|
133
|
+
Employee.manager=User.identifier
|
|
134
|
+
ORDER BY Employee.startDate desc, Employee.department asc
|
|
135
|
+
limit 10 offset 2`;
|
|
136
|
+
|
|
137
|
+
const testQuery7 = `SELECT
|
|
138
|
+
Employee.identifier AS EmployeeID,
|
|
139
|
+
User.identifier AS userID
|
|
140
|
+
from Employee
|
|
141
|
+
INNER JOIN User ON
|
|
142
|
+
Employee.manager=User.identifier
|
|
143
|
+
limit 10 offset 0`;
|
|
144
|
+
|
|
145
|
+
const testQuery8 = `SELECT
|
|
146
|
+
Employee.identifier AS EmployeeID,
|
|
147
|
+
Employee.startDate AS startDate,
|
|
148
|
+
User.identifier AS userID,
|
|
149
|
+
Employee.department as department_a
|
|
150
|
+
from Employee
|
|
151
|
+
INNER JOIN User ON
|
|
152
|
+
Employee.manager=User.identifier
|
|
153
|
+
ORDER BY Employee.startDate desc, Employee.department asc
|
|
154
|
+
limit 10 offset 0`;
|
|
155
|
+
|
|
156
|
+
const testQuery9 = `SELECT *
|
|
157
|
+
from Employee
|
|
158
|
+
ORDER BY startDate desc
|
|
159
|
+
limit 10 offset 0`;
|
|
160
|
+
|
|
161
|
+
const testQuery10 = `SELECT
|
|
162
|
+
Employee.identifier AS EmployeeID,
|
|
163
|
+
Employee.startDate AS startDate,
|
|
164
|
+
User.identifier AS userID
|
|
165
|
+
from
|
|
166
|
+
(SELECT
|
|
167
|
+
identifier AS EmployeeID,
|
|
168
|
+
manager,
|
|
169
|
+
startDate
|
|
170
|
+
FROM
|
|
171
|
+
Employee
|
|
172
|
+
WHERE
|
|
173
|
+
startDate > '2020-01-01' -- Example condition in subquery
|
|
174
|
+
) AS e
|
|
175
|
+
INNER JOIN User ON
|
|
176
|
+
Employee.manager=User.identifier
|
|
177
|
+
ORDER BY Employee.startDate desc
|
|
178
|
+
limit 10 offset 0`;
|
|
179
|
+
|
|
180
|
+
const testQuery11 = `SELECT
|
|
181
|
+
Employee.identifier AS EmployeeID,
|
|
182
|
+
Employee.startDate AS startDate,
|
|
183
|
+
User.identifier AS userID,
|
|
184
|
+
Employee.manager,
|
|
185
|
+
Employee.department AS department_a,
|
|
186
|
+
ProductSub.product_identifier AS ProductID,
|
|
187
|
+
RandomTable.random_value AS RandomValue
|
|
188
|
+
FROM Employee
|
|
189
|
+
INNER JOIN User ON Employee.manager = User.identifier
|
|
190
|
+
LEFT JOIN (
|
|
191
|
+
SELECT identifier AS product_identifier, name
|
|
192
|
+
FROM Product
|
|
193
|
+
WHERE name LIKE 'a%'
|
|
194
|
+
) AS ProductSub ON ProductSub.product_identifier = 'a'
|
|
195
|
+
CROSS JOIN (SELECT 'useless_value' AS random_value) AS RandomTable
|
|
196
|
+
CROSS JOIN (SELECT 'another_useless_value' AS another_useless_value) AS AnotherRandomTable
|
|
197
|
+
ORDER BY Employee.startDate DESC, Employee.department ASC
|
|
198
|
+
LIMIT 10 OFFSET 0`;
|
|
199
|
+
|
|
200
|
+
const testQuery12 = `SELECT
|
|
201
|
+
Employee.identifier AS EmployeeID,
|
|
202
|
+
Employee.startDate AS startDate,
|
|
203
|
+
User.identifier AS userID,
|
|
204
|
+
Employee.department AS department_a,
|
|
205
|
+
ProductSub.product_identifier AS ProductID,
|
|
206
|
+
RandomTable.random_value AS RandomValue
|
|
207
|
+
FROM Employee
|
|
208
|
+
LEFT JOIN (
|
|
209
|
+
SELECT identifier AS product_identifier, name
|
|
210
|
+
FROM Product
|
|
211
|
+
WHERE name LIKE 'a%'
|
|
212
|
+
-- ) AS ProductSub ON Employee.identifier = ProductSub.product_identifier
|
|
213
|
+
) AS ProductSub ON ProductSub.product_identifier = 'a'
|
|
214
|
+
ORDER BY Employee.startDate DESC, Employee.department ASC
|
|
215
|
+
LIMIT 10 OFFSET 0`;
|
|
216
|
+
|
|
217
|
+
const testQuery13 = `SELECT
|
|
218
|
+
Employee.identifier AS EmployeeID,
|
|
219
|
+
Employee.startDate AS startDate,
|
|
220
|
+
User.identifier AS userID,
|
|
221
|
+
Employee.department AS department_a
|
|
222
|
+
FROM Employee
|
|
223
|
+
INNER JOIN User ON Employee.manager = User.identifier
|
|
224
|
+
ORDER BY Employee.startDate DESC, Employee.department ASC
|
|
225
|
+
LIMIT 10 OFFSET 0`;
|
|
226
|
+
|
|
227
|
+
const testQuery14 = `SELECT
|
|
228
|
+
identifier AS product_identifier,
|
|
229
|
+
name
|
|
230
|
+
FROM Product
|
|
231
|
+
WHERE name LIKE 'a';`;
|
|
232
|
+
|
|
233
|
+
const testQuery15 = `SELECT
|
|
234
|
+
Employee.identifier AS EmployeeID,
|
|
235
|
+
Employee.startDate AS startDate,
|
|
236
|
+
User.identifier AS userID,
|
|
237
|
+
Employee.manager,
|
|
238
|
+
Employee.department AS department_a,
|
|
239
|
+
ProductSub.product_identifier AS ProductID,
|
|
240
|
+
RandomTable.random_value AS RandomValue
|
|
241
|
+
FROM Employee
|
|
242
|
+
INNER JOIN User ON Employee.manager = User.identifier
|
|
243
|
+
LEFT JOIN (
|
|
244
|
+
SELECT identifier AS product_identifier, name
|
|
245
|
+
FROM Product
|
|
246
|
+
WHERE name LIKE 'a%'
|
|
247
|
+
) AS ProductSub ON ProductSub.product_identifier = 'a'
|
|
248
|
+
CROSS JOIN (SELECT 'useless_value' AS random_value) AS RandomTable
|
|
249
|
+
CROSS JOIN (SELECT 'another_useless_value' AS another_useless_value) AS AnotherRandomTable
|
|
250
|
+
WHERE
|
|
251
|
+
Employee.department = 'Sales'
|
|
252
|
+
AND Employee.startDate >= '2020-01-01'
|
|
253
|
+
AND Employee.manager IS NOT NULL
|
|
254
|
+
|
|
255
|
+
-- OR condition with grouping
|
|
256
|
+
AND (
|
|
257
|
+
Employee.identifier IN (SELECT identifier FROM Employee WHERE department = 'Engineering')
|
|
258
|
+
OR Employee.identifier IN (SELECT identifier FROM Employee WHERE department = 'Marketing')
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
-- Nested conditions with OR and AND mixed
|
|
262
|
+
AND (
|
|
263
|
+
(RandomTable.random_value = 'useless_value' AND AnotherRandomTable.another_useless_value = 'another_useless_value')
|
|
264
|
+
OR
|
|
265
|
+
(Employee.department = 'HR' AND Employee.startDate < '2015-01-01')
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
-- Another OR group with nested conditions
|
|
269
|
+
AND (
|
|
270
|
+
ProductSub.product_identifier IS NOT NULL
|
|
271
|
+
OR (Employee.manager = User.identifier AND User.identifier IS NOT NULL)
|
|
272
|
+
)
|
|
273
|
+
ORDER BY Employee.startDate DESC, Employee.department ASC
|
|
274
|
+
LIMIT 10 OFFSET 0;`;
|
|
275
|
+
|
|
276
|
+
const testQuery16 = `SELECT
|
|
277
|
+
Employee.identifier AS EmployeeID,
|
|
278
|
+
Employee.startDate AS startDate,
|
|
279
|
+
User.identifier AS userID,
|
|
280
|
+
Employee.manager,
|
|
281
|
+
Employee.department AS department_a,
|
|
282
|
+
ProductSub.product_identifier AS ProductID,
|
|
283
|
+
RandomTable.random_value AS RandomValue
|
|
284
|
+
FROM Employee
|
|
285
|
+
INNER JOIN User ON Employee.manager = User.identifier
|
|
286
|
+
LEFT JOIN (
|
|
287
|
+
SELECT identifier AS product_identifier, name
|
|
288
|
+
FROM Product
|
|
289
|
+
WHERE name LIKE 'a%'
|
|
290
|
+
) AS ProductSub ON ProductSub.product_identifier = 'a'
|
|
291
|
+
CROSS JOIN (SELECT 'useless_value' AS random_value) AS RandomTable
|
|
292
|
+
CROSS JOIN (SELECT 'another_useless_value' AS another_useless_value) AS AnotherRandomTable
|
|
293
|
+
WHERE
|
|
294
|
+
Employee.department = 'Sales'
|
|
295
|
+
AND Employee.startDate >= '2020-01-01'
|
|
296
|
+
ORDER BY Employee.startDate DESC, Employee.department ASC
|
|
297
|
+
LIMIT 10 OFFSET 0;`;
|
|
298
|
+
|
|
299
|
+
const testQuery17 = `SELECT
|
|
300
|
+
Employee.identifier AS EmployeeID,
|
|
301
|
+
Employee.startDate AS startDate,
|
|
302
|
+
User.identifier AS userID,
|
|
303
|
+
Employee.manager,
|
|
304
|
+
Employee.department AS department_a,
|
|
305
|
+
ProductSub.product_identifier AS ProductID,
|
|
306
|
+
RandomTable.random_value AS RandomValue
|
|
307
|
+
FROM Employee
|
|
308
|
+
INNER JOIN User ON Employee.manager = User.identifier
|
|
309
|
+
LEFT JOIN (
|
|
310
|
+
SELECT identifier AS product_identifier, name
|
|
311
|
+
FROM Product
|
|
312
|
+
WHERE name LIKE 'a%'
|
|
313
|
+
) AS ProductSub ON ProductSub.product_identifier = 'a'
|
|
314
|
+
CROSS JOIN (SELECT 'useless_value' AS random_value) AS RandomTable
|
|
315
|
+
CROSS JOIN (SELECT 'another_useless_value') AS AnotherRandomTable
|
|
316
|
+
WHERE
|
|
317
|
+
Employee.department = 'Sales'
|
|
318
|
+
AND Employee.startDate >= '2020-01-01'
|
|
319
|
+
AND Employee.manager IS NOT NULL
|
|
320
|
+
OR (Employee.manager = User.identifier AND User.identifier IS NOT NULL)
|
|
321
|
+
ORDER BY Employee.startDate DESC, Employee.department ASC
|
|
322
|
+
LIMIT 10 OFFSET 0;`;
|
|
323
|
+
|
|
324
|
+
const testQuery18 = `select *, Employee.* from Employee;`;
|
|
325
|
+
|
|
326
|
+
const testQuery19 = `select Something.*
|
|
327
|
+
from Something
|
|
328
|
+
LEFT JOIN Prod ON 1 = 1
|
|
329
|
+
where
|
|
330
|
+
Something.department = 'Sales';`;
|
|
331
|
+
|
|
332
|
+
// =============================================================================
|
|
333
|
+
// HEAVILY-NESTED FUNCTION FIXTURES — for layout stress-testing the search
|
|
334
|
+
// builder. Set OVERRIDE_TEST_QUERY (below) to one of these to bypass the
|
|
335
|
+
// entity's saved query and load a known-bad-layout case instead.
|
|
336
|
+
// =============================================================================
|
|
337
|
+
|
|
338
|
+
// Heavy scalar-function nesting in SELECT + WHERE.
|
|
339
|
+
// Exercises: nested calls in selection columns, function-of-function in WHERE.
|
|
340
|
+
const testQueryNestedScalars = `SELECT
|
|
341
|
+
UPPER(COALESCE(CONCAT(Employee.department, '-', LOWER(Employee.identifier)), 'NONE')) AS label,
|
|
342
|
+
CONCAT(UPPER(Employee.department), ' (', COALESCE(Employee.manager, 'no manager'), ')') AS departmentLine,
|
|
343
|
+
Employee.identifier AS EmployeeID
|
|
344
|
+
FROM Employee
|
|
345
|
+
WHERE LENGTH(COALESCE(Employee.manager, '')) > 0
|
|
346
|
+
AND UPPER(Employee.department) = UPPER('sales')
|
|
347
|
+
ORDER BY UPPER(Employee.department) ASC
|
|
348
|
+
LIMIT 10 OFFSET 0;`;
|
|
349
|
+
|
|
350
|
+
// Heavy table-valued function nesting in FROM + JOINs.
|
|
351
|
+
// Exercises: function as the main FROM source, multiple JOINs whose source
|
|
352
|
+
// is itself a function, an existing JOIN function call referenced via alias.
|
|
353
|
+
const testQueryNestedTableFunctions = `SELECT
|
|
354
|
+
Employee.identifier AS EmployeeID,
|
|
355
|
+
series.value AS seriesValue,
|
|
356
|
+
arr.value AS arrValue
|
|
357
|
+
FROM Employee
|
|
358
|
+
LEFT JOIN generate_series(1, 5, 1) AS series ON series.value > 0
|
|
359
|
+
LEFT JOIN unnest('[1,2,3]') AS arr ON arr.value = series.value
|
|
360
|
+
LEFT JOIN unnest(json_build_object('k', Employee.identifier)) AS payload ON 1 = 1
|
|
361
|
+
WHERE Employee.department = 'Sales'
|
|
362
|
+
ORDER BY Employee.identifier ASC
|
|
363
|
+
LIMIT 10 OFFSET 0;`;
|
|
364
|
+
|
|
365
|
+
// Mixes everything: nested scalars in SELECT, table-valued in FROM/JOIN,
|
|
366
|
+
// nested AND/OR in WHERE, ordering on a function expression.
|
|
367
|
+
const testQueryEverything = `SELECT
|
|
368
|
+
UPPER(COALESCE(Employee.department, 'UNKNOWN')) AS deptLabel,
|
|
369
|
+
CONCAT(Employee.identifier, '-', COALESCE(Employee.manager, 'root')) AS chain,
|
|
370
|
+
series.value AS seriesValue,
|
|
371
|
+
Product.name AS productName
|
|
372
|
+
FROM Employee
|
|
373
|
+
INNER JOIN User ON Employee.manager = User.identifier
|
|
374
|
+
LEFT JOIN Product ON Product.name LIKE 'a%'
|
|
375
|
+
LEFT JOIN generate_series(1, 3, 1) AS series ON series.value >= 1
|
|
376
|
+
LEFT JOIN unnest('[1,2]') AS arr ON arr.value = series.value
|
|
377
|
+
WHERE
|
|
378
|
+
UPPER(Employee.department) = 'SALES'
|
|
379
|
+
AND (
|
|
380
|
+
LENGTH(COALESCE(Employee.manager, '')) > 0
|
|
381
|
+
OR Employee.startDate >= '2020-01-01'
|
|
382
|
+
)
|
|
383
|
+
AND COALESCE(Product.name, '') != ''
|
|
384
|
+
ORDER BY UPPER(Employee.department) ASC, Employee.startDate DESC
|
|
385
|
+
LIMIT 25 OFFSET 0;`;
|
|
386
|
+
|
|
387
|
+
// Minimal: a single JOIN whose source is a table-valued function. The smallest
|
|
388
|
+
// fixture that exercises both the Merge-strategy alignment (function source =>
|
|
389
|
+
// tall data-source side) and the duplicate-`unnest` dedup in the dropdown.
|
|
390
|
+
const testQueryJoinFunction = `SELECT Employee.identifier AS EmployeeID
|
|
391
|
+
FROM Employee
|
|
392
|
+
LEFT JOIN unnest('[1,2,3]') AS arr ON 1 = 1;`;
|
|
393
|
+
|
|
394
|
+
// Deep scalar-function nesting on literals only — avoids any schema-dependent
|
|
395
|
+
// column refs that could fail hydration. The point is to render functions
|
|
396
|
+
// inside functions inside functions across SELECT, WHERE, and ORDER BY so we
|
|
397
|
+
// can see how the layout holds up at multiple nesting depths.
|
|
398
|
+
const testQueryDeepNestedFunctions = `SELECT
|
|
399
|
+
UPPER(COALESCE(LOWER('HELLO'), 'fallback')) AS msg1,
|
|
400
|
+
CONCAT(UPPER('a'), '-', LOWER('B'), '-', COALESCE('c', 'd')) AS msg2,
|
|
401
|
+
LENGTH(CONCAT(UPPER(LOWER('mixed')), '_', COALESCE('x', 'y'))) AS len1
|
|
402
|
+
FROM unnest('[1,2,3]') AS arr
|
|
403
|
+
WHERE LENGTH(COALESCE(UPPER('value'), '')) > 0
|
|
404
|
+
AND UPPER(LOWER('a')) = LOWER(UPPER('A'))
|
|
405
|
+
ORDER BY UPPER(LOWER('z')) ASC
|
|
406
|
+
LIMIT 5 OFFSET 0;`;
|
|
407
|
+
|
|
408
|
+
// Two unnest sources fed by JSON-array literal strings — exercises the array
|
|
409
|
+
// literal hydration path and gives the layout engine two parallel function
|
|
410
|
+
// data-sources to render side-by-side as JOINs.
|
|
411
|
+
const testQueryArrays = `SELECT a.value AS a_val, b.value AS b_val
|
|
412
|
+
FROM unnest('[1,2,3]') AS a
|
|
413
|
+
LEFT JOIN unnest('[4,5,6]') AS b ON a.value = b.value
|
|
414
|
+
WHERE a.value > 0
|
|
415
|
+
ORDER BY a.value ASC
|
|
416
|
+
LIMIT 10 OFFSET 0;`;
|
|
417
|
+
|
|
418
|
+
// Nested arrays via nested unnest calls — the inner unnest emits a row
|
|
419
|
+
// collection, then the outer unnest expands that. Layout-wise this is the
|
|
420
|
+
// most aggressive table-valued nesting we test.
|
|
421
|
+
const testQueryNestedArrays = `SELECT outer_arr.value AS v
|
|
422
|
+
FROM unnest('[[1,2],[3,4],[5,6]]') AS outer_arr
|
|
423
|
+
WHERE LENGTH(UPPER(COALESCE(LOWER('hello'), 'x'))) > 0
|
|
424
|
+
ORDER BY outer_arr.value ASC
|
|
425
|
+
LIMIT 5 OFFSET 0;`;
|
|
426
|
+
|
|
427
|
+
// Subquery in FROM (nested SELECT) + WHERE filter inside it. Exercises the
|
|
428
|
+
// nested-search-statement rendering and ensures the parent's calculateAst
|
|
429
|
+
// tolerates a child query that's actually populated.
|
|
430
|
+
const testQueryNestedSelect = `SELECT sub.identifier AS empId
|
|
431
|
+
FROM (
|
|
432
|
+
SELECT Employee.id AS identifier
|
|
433
|
+
FROM Employee
|
|
434
|
+
WHERE Employee.id IS NOT NULL
|
|
435
|
+
) AS sub
|
|
436
|
+
ORDER BY sub.identifier ASC
|
|
437
|
+
LIMIT 5 OFFSET 0;`;
|
|
438
|
+
|
|
439
|
+
// "Everything bagel" — combines deep nested functions in SELECT and WHERE,
|
|
440
|
+
// a nested SELECT in FROM, an array-literal in a JOIN's unnest source, and
|
|
441
|
+
// ordering on a function expression. Useful for spotting layout regressions
|
|
442
|
+
// that only show up under combined load.
|
|
443
|
+
const testQueryEverythingBagel = `SELECT
|
|
444
|
+
UPPER(COALESCE(LOWER(sub.identifier), 'unknown')) AS upperId,
|
|
445
|
+
arr.value AS arrayValue
|
|
446
|
+
FROM (
|
|
447
|
+
SELECT Employee.id AS identifier
|
|
448
|
+
FROM Employee
|
|
449
|
+
WHERE Employee.id IS NOT NULL
|
|
450
|
+
) AS sub
|
|
451
|
+
LEFT JOIN unnest('[1,2,3]') AS arr ON arr.value > 0
|
|
452
|
+
WHERE LENGTH(UPPER(COALESCE(sub.identifier, ''))) > 0
|
|
453
|
+
AND UPPER(LOWER('x')) != LOWER(UPPER('Y'))
|
|
454
|
+
ORDER BY UPPER(LOWER(sub.identifier)) ASC
|
|
455
|
+
LIMIT 10 OFFSET 0;`;
|
|
456
|
+
|
|
457
|
+
// External-input interpolation stress fixture. The dialog's saved Search
|
|
458
|
+
// entity has 7 input maps with the exact UUIDs below; each `{{::id}}`
|
|
459
|
+
// reference round-trips through hydrate → ValueRef → toQuery. Items below
|
|
460
|
+
// exercise interpolation in every position where a value can appear: SELECT
|
|
461
|
+
// projections (literal + function args), JOIN's array literal can't take an
|
|
462
|
+
// interpolation but the JOIN's ON RHS can, WHERE LHS/RHS, function args
|
|
463
|
+
// inside WHERE, ORDER BY (via a function expression), and LIMIT / OFFSET.
|
|
464
|
+
//
|
|
465
|
+
// Email (String) : efb7b373-bc11-4b5b-8035-720b82fdb9e6
|
|
466
|
+
// First number value (Number): 7a859d02-6c8a-490c-9dee-327300787808
|
|
467
|
+
// Second number value (Number): 4072c906-5937-4a0e-b4fb-51b4b1c74dc1
|
|
468
|
+
// Some boolean (Boolean) : 223f61d4-dd05-4cf7-9f21-957c41a44425
|
|
469
|
+
// Some external ID (UUID) : de00a2d7-7b63-4828-bce3-885aeea55dda
|
|
470
|
+
// External enum input (Enum): 9c3dd62b-2bcb-4341-bc3e-973db698a0f8
|
|
471
|
+
// Explicitly empty input (Null): ddf2890c-593a-4663-97ed-5d5efad86eb4
|
|
472
|
+
const testQueryInterpolationEverywhere = `SELECT
|
|
473
|
+
Employee.id AS empId,
|
|
474
|
+
'{{::efb7b373-bc11-4b5b-8035-720b82fdb9e6}}' AS emailLiteralProjection,
|
|
475
|
+
CONCAT('prefix-', '{{::efb7b373-bc11-4b5b-8035-720b82fdb9e6}}', '-suffix') AS labelWithEmail,
|
|
476
|
+
UPPER(COALESCE('{{::efb7b373-bc11-4b5b-8035-720b82fdb9e6}}', 'fallback')) AS upperEmail,
|
|
477
|
+
LENGTH('{{::9c3dd62b-2bcb-4341-bc3e-973db698a0f8}}') AS enumLength
|
|
478
|
+
FROM Employee
|
|
479
|
+
LEFT JOIN unnest('[1,2,3]') AS arr ON arr.value > '{{::7a859d02-6c8a-490c-9dee-327300787808}}'
|
|
480
|
+
WHERE Employee.id != '{{::de00a2d7-7b63-4828-bce3-885aeea55dda}}'
|
|
481
|
+
AND LENGTH('{{::efb7b373-bc11-4b5b-8035-720b82fdb9e6}}') > '{{::4072c906-5937-4a0e-b4fb-51b4b1c74dc1}}'
|
|
482
|
+
AND UPPER('{{::9c3dd62b-2bcb-4341-bc3e-973db698a0f8}}') != UPPER('placeholder')
|
|
483
|
+
ORDER BY UPPER(COALESCE('{{::efb7b373-bc11-4b5b-8035-720b82fdb9e6}}', '')) ASC
|
|
484
|
+
LIMIT '{{::7a859d02-6c8a-490c-9dee-327300787808}}'
|
|
485
|
+
OFFSET '{{::4072c906-5937-4a0e-b4fb-51b4b1c74dc1}}';`;
|
|
486
|
+
|
|
487
|
+
// Round-trip canary: a fixture that uses ONLY columns that exist on the real
|
|
488
|
+
// schema (Employee.id, not the non-existent Employee.identifier) so that
|
|
489
|
+
// hydrate → toQuery → re-hydrate preserves every field. Useful to spot
|
|
490
|
+
// regressions where the stringified SQL produces a shape the parser+hydrator
|
|
491
|
+
// can't reconstruct identically.
|
|
492
|
+
const testQueryRoundTripCanary = `SELECT
|
|
493
|
+
Employee.id AS empId,
|
|
494
|
+
UPPER(COALESCE(Employee.id, '')) AS upperId,
|
|
495
|
+
CONCAT(Employee.id, '-tag') AS taggedId
|
|
496
|
+
FROM Employee
|
|
497
|
+
LEFT JOIN unnest('[1,2,3]') AS arr ON 1 = 1
|
|
498
|
+
WHERE Employee.id IS NOT NULL
|
|
499
|
+
AND LENGTH(UPPER(Employee.id)) > 0
|
|
500
|
+
ORDER BY UPPER(Employee.id) ASC
|
|
501
|
+
LIMIT 10 OFFSET 0;`;
|
|
502
|
+
|
|
503
|
+
// =============================================================================
|
|
504
|
+
// Set this to one of the testQuery* constants above to bypass the saved
|
|
505
|
+
// entity query and load that fixture instead. Useful for stress-testing the
|
|
506
|
+
// dialog layout against intentionally-pathological queries. Leave at null to
|
|
507
|
+
// use the actual saved query.
|
|
508
|
+
// =============================================================================
|
|
509
|
+
const OVERRIDE_TEST_QUERY: string | null = null; // testQueryInterpolationEverywhere;
|
|
510
|
+
|
|
511
|
+
// Visits all nodes in the syntax tree and applies a callback to each node
|
|
512
|
+
// If the callback returns a value, the value will replace the node
|
|
513
|
+
// If the callback returns null, the node will be removed
|
|
514
|
+
// If the callback returns undefined, the node will be left as is
|
|
515
|
+
export function nodeVisitor(
|
|
516
|
+
node: SqlAstNode,
|
|
517
|
+
callbacks: {
|
|
518
|
+
beforeChildrenCallback?: (node: SqlAstNode) => SqlAstNode | null | void;
|
|
519
|
+
afterChildrenCallback?: (node: SqlAstNode) => SqlAstNode | null | void;
|
|
520
|
+
}
|
|
521
|
+
): SqlAstNode | null | void {
|
|
522
|
+
if (!node) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const beforeCallbackResult = callbacks?.beforeChildrenCallback?.(node);
|
|
527
|
+
|
|
528
|
+
if (beforeCallbackResult === null) {
|
|
529
|
+
return null;
|
|
530
|
+
} else if (beforeCallbackResult) {
|
|
531
|
+
return beforeCallbackResult;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const keys: string[] = Object.keys(node);
|
|
535
|
+
|
|
536
|
+
keys.forEach((key) => {
|
|
537
|
+
// @ts-ignore
|
|
538
|
+
const value = node[key];
|
|
539
|
+
|
|
540
|
+
if (Array.isArray(value)) {
|
|
541
|
+
value.forEach((item, index) => {
|
|
542
|
+
if (item && typeof item === 'object') {
|
|
543
|
+
const newItem = nodeVisitor(item, callbacks);
|
|
544
|
+
|
|
545
|
+
if (newItem === null) {
|
|
546
|
+
value.splice(index, 1);
|
|
547
|
+
} else if (newItem) {
|
|
548
|
+
value[index] = newItem;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
} else if (value && typeof value === 'object') {
|
|
553
|
+
const newValue = nodeVisitor(value, callbacks);
|
|
554
|
+
|
|
555
|
+
if (newValue === null) {
|
|
556
|
+
// @ts-ignore
|
|
557
|
+
delete node[key];
|
|
558
|
+
} else if (newValue) {
|
|
559
|
+
// @ts-ignore
|
|
560
|
+
node[key] = newValue;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
return callbacks?.afterChildrenCallback?.(node);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Recursively visits all nodes in the syntax tree and adds a uuid 'id' property to each node
|
|
569
|
+
export function addIdsToASTNodes(sqlAstNode: SqlAstNode) {
|
|
570
|
+
nodeVisitor(sqlAstNode, {
|
|
571
|
+
beforeChildrenCallback: (node) => {
|
|
572
|
+
const id = ProjectState.UUID.uuid();
|
|
573
|
+
|
|
574
|
+
// @ts-ignore
|
|
575
|
+
node.id = id;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// addIdsToASTNodes(cst);
|
|
581
|
+
|
|
582
|
+
// convert all keywords to uppercase
|
|
583
|
+
export const toUpper = cstVisitor({
|
|
584
|
+
keyword: (kw) => {
|
|
585
|
+
kw.text = kw.text.toUpperCase();
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
export const toLower = cstVisitor({
|
|
590
|
+
keyword: (kw) => {
|
|
591
|
+
kw.text = kw.text.toLowerCase();
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
export function replaceNode(
|
|
596
|
+
parentNode: SqlAstNode,
|
|
597
|
+
nodeToReplace: SqlAstNode,
|
|
598
|
+
newNode: SqlAstNode
|
|
599
|
+
) {
|
|
600
|
+
nodeVisitor(parentNode, {
|
|
601
|
+
beforeChildrenCallback: (node) => {
|
|
602
|
+
if (
|
|
603
|
+
!!node &&
|
|
604
|
+
!!nodeToReplace &&
|
|
605
|
+
(node === nodeToReplace ||
|
|
606
|
+
// @ts-ignore
|
|
607
|
+
node?.id === nodeToReplace?.id)
|
|
608
|
+
) {
|
|
609
|
+
return newNode;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export enum SearchStatementBuilderTabType {
|
|
616
|
+
MainSearchStatement = 'main-search-statement',
|
|
617
|
+
NewInputTab = 'new-input',
|
|
618
|
+
InputDeclarationTab = 'input-declaration-tab',
|
|
619
|
+
NestedQuery = 'nested-query'
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export interface ISearchStatementTab {
|
|
623
|
+
entity: SearchStatementBuilderTabType;
|
|
624
|
+
// Any extra data that the tab might need
|
|
625
|
+
[key: string]: any;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export interface ITabManager {
|
|
629
|
+
addTab: (tab: ISearchStatementTab, navigate?: boolean) => void;
|
|
630
|
+
navigateToTab: (tab: ISearchStatementTab) => void;
|
|
631
|
+
navigateToTabIndex: (index: number) => void;
|
|
632
|
+
popTab: () => void;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export interface ISearchStatementBaseProps extends IDialogProps {
|
|
636
|
+
project: EditorService;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export interface ISearchStatementContext {
|
|
640
|
+
tabManager: ITabManager;
|
|
641
|
+
searchStatementState: SearchStatementState;
|
|
642
|
+
entity: SearchState;
|
|
643
|
+
dataSources: DataSource[];
|
|
644
|
+
selections: (ColumnRef | AllColumnsSelector | FunctionCall)[];
|
|
645
|
+
selectionsUpdateKey: number;
|
|
646
|
+
aggregations: AggregationStatement[];
|
|
647
|
+
aggregationsUpdateKey: number;
|
|
648
|
+
addAggregation: (value: AggregationStatement) => void;
|
|
649
|
+
removeAggregation: (value: AggregationStatement) => void;
|
|
650
|
+
editAggregation: (value: AggregationStatement) => void;
|
|
651
|
+
mainDataSource: DataSource | null;
|
|
652
|
+
setMainDataSource: (value: DataSource) => void;
|
|
653
|
+
setOffset: (value: SearchStatementLiteralValue | ValueRef) => void;
|
|
654
|
+
setLimit: (value: SearchStatementLiteralValue | ValueRef) => void;
|
|
655
|
+
setAsList: (value: boolean) => void;
|
|
656
|
+
sorting: SortStatement[];
|
|
657
|
+
sortingUpdateKey: number;
|
|
658
|
+
addSorting: (value: SortStatement) => void;
|
|
659
|
+
editSorting: (value: SortStatement) => void;
|
|
660
|
+
removeSorting: (value: SortStatement) => void;
|
|
661
|
+
addSelection: (
|
|
662
|
+
value: ColumnRef | AllColumnsSelector | FunctionCall
|
|
663
|
+
) => void;
|
|
664
|
+
editSelection: (
|
|
665
|
+
value: ColumnRef | AllColumnsSelector | FunctionCall
|
|
666
|
+
) => void;
|
|
667
|
+
removeSelection: (
|
|
668
|
+
value: ColumnRef | AllColumnsSelector | FunctionCall
|
|
669
|
+
) => void;
|
|
670
|
+
resolveDatabaseClient: () => Database | null;
|
|
671
|
+
setWhere: (value: WhereStatement | null) => void;
|
|
672
|
+
where: WhereStatement | null;
|
|
673
|
+
whereUpdateKey: number;
|
|
674
|
+
offset: SearchStatementLiteralValue | ValueRef | null;
|
|
675
|
+
offsetUpdateKey: number;
|
|
676
|
+
limit: SearchStatementLiteralValue | ValueRef | null;
|
|
677
|
+
limitUpdateKey: number;
|
|
678
|
+
dataSourcesUpdateKey: number;
|
|
679
|
+
asList: boolean;
|
|
680
|
+
project: EditorService;
|
|
681
|
+
sqlAST: SQLAST | null;
|
|
682
|
+
hydrationReady: boolean;
|
|
683
|
+
isNested: boolean;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
export const SearchStatementContext = createContext<ISearchStatementContext>({
|
|
687
|
+
tabManager: {
|
|
688
|
+
addTab: () => {},
|
|
689
|
+
navigateToTab: () => {},
|
|
690
|
+
navigateToTabIndex: () => {},
|
|
691
|
+
popTab: () => {}
|
|
692
|
+
},
|
|
693
|
+
searchStatementState: null,
|
|
694
|
+
entity: null,
|
|
695
|
+
dataSources: [],
|
|
696
|
+
selections: [],
|
|
697
|
+
selectionsUpdateKey: 0,
|
|
698
|
+
aggregations: [],
|
|
699
|
+
aggregationsUpdateKey: 0,
|
|
700
|
+
addAggregation: () => {},
|
|
701
|
+
removeAggregation: () => {},
|
|
702
|
+
editAggregation: () => {},
|
|
703
|
+
mainDataSource: null,
|
|
704
|
+
setMainDataSource: () => {},
|
|
705
|
+
setOffset: () => {},
|
|
706
|
+
setLimit: () => {},
|
|
707
|
+
setAsList: () => {},
|
|
708
|
+
sorting: [],
|
|
709
|
+
sortingUpdateKey: 0,
|
|
710
|
+
addSorting: () => {},
|
|
711
|
+
editSorting: () => {},
|
|
712
|
+
removeSorting: () => {},
|
|
713
|
+
addSelection: () => {},
|
|
714
|
+
editSelection: () => {},
|
|
715
|
+
removeSelection: () => {},
|
|
716
|
+
resolveDatabaseClient: () => null,
|
|
717
|
+
setWhere: () => {},
|
|
718
|
+
where: null,
|
|
719
|
+
whereUpdateKey: 0,
|
|
720
|
+
offset: null,
|
|
721
|
+
offsetUpdateKey: 0,
|
|
722
|
+
limit: null,
|
|
723
|
+
limitUpdateKey: 0,
|
|
724
|
+
dataSourcesUpdateKey: 0,
|
|
725
|
+
asList: true,
|
|
726
|
+
project: null,
|
|
727
|
+
sqlAST: null,
|
|
728
|
+
hydrationReady: false,
|
|
729
|
+
isNested: false
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
export const useSearchStatementContext = () => {
|
|
733
|
+
const context = useContext(SearchStatementContext);
|
|
734
|
+
|
|
735
|
+
if (!context) {
|
|
736
|
+
throw new Error(
|
|
737
|
+
'useSearchStatementContext must be used within an SearchStatementProvider'
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return context;
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
const SearchStatementProvider = ({
|
|
745
|
+
children,
|
|
746
|
+
project,
|
|
747
|
+
ready,
|
|
748
|
+
tabManager
|
|
749
|
+
}: {
|
|
750
|
+
children: ReactNode;
|
|
751
|
+
project: EditorService;
|
|
752
|
+
ready: boolean;
|
|
753
|
+
tabManager: ITabManager;
|
|
754
|
+
}) => {
|
|
755
|
+
const { entityId } = useParams<{
|
|
756
|
+
entityType: EntityType;
|
|
757
|
+
entityId: EntityId;
|
|
758
|
+
}>();
|
|
759
|
+
|
|
760
|
+
const entity: SearchState | null = project?.logic.get(
|
|
761
|
+
entityId
|
|
762
|
+
) as SearchState | null;
|
|
763
|
+
|
|
764
|
+
const [sqlAST, _setSqlAST] = useState<SQLAST | null>(null);
|
|
765
|
+
|
|
766
|
+
const searchStatementState: SearchStatementState | null = useMemo(() => {
|
|
767
|
+
if (!entity || !project) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// If an override fixture is set, use it in place of the entity's saved
|
|
772
|
+
// query so the builder loads with a controlled, complex test case.
|
|
773
|
+
const sourceQuery = OVERRIDE_TEST_QUERY ?? entity.query;
|
|
774
|
+
|
|
775
|
+
if (OVERRIDE_TEST_QUERY) {
|
|
776
|
+
Logger.warn(
|
|
777
|
+
'[SearchStatementProvider] Using OVERRIDE_TEST_QUERY instead of the saved entity query. Set it back to null to load real data.'
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const sanitizedQuery =
|
|
782
|
+
searchStatementUtils.sanitizeQuery(sourceQuery) || '';
|
|
783
|
+
|
|
784
|
+
const initialAst = sanitizedQuery
|
|
785
|
+
? parse(sanitizedQuery, {
|
|
786
|
+
dialect: 'sqlite'
|
|
787
|
+
})
|
|
788
|
+
: // Initializing the state with a default query
|
|
789
|
+
null;
|
|
790
|
+
|
|
791
|
+
const hydratedStateConfig =
|
|
792
|
+
searchStatementUtils.hydrateSearchStatementState(
|
|
793
|
+
initialAst,
|
|
794
|
+
project.logic,
|
|
795
|
+
entity
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
if (!!hydratedStateConfig.errors.length) {
|
|
799
|
+
popupNotification({
|
|
800
|
+
color: 'error',
|
|
801
|
+
text: `Error:\n${hydratedStateConfig.errors
|
|
802
|
+
.map(
|
|
803
|
+
(error) =>
|
|
804
|
+
`"${error.name}" ${
|
|
805
|
+
error.entity === 'table' ? 'entity' : 'property'
|
|
806
|
+
} not found`
|
|
807
|
+
)
|
|
808
|
+
.join('\n')}`,
|
|
809
|
+
delay: 4000
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return hydratedStateConfig.state;
|
|
814
|
+
}, [project, entityId]);
|
|
815
|
+
|
|
816
|
+
const [dataSources, _setDataSources] = useState<DataSource[]>(
|
|
817
|
+
searchStatementState?.dataSources || []
|
|
818
|
+
);
|
|
819
|
+
const [dataSourcesUpdateKey, setDataSourcesUpdateKey] = useState(0);
|
|
820
|
+
|
|
821
|
+
const [where, _setWhere] = useState<WhereStatement | null>(
|
|
822
|
+
searchStatementState?.where || null
|
|
823
|
+
);
|
|
824
|
+
const [whereUpdateKey, setWhereUpdateKey] = useState(0);
|
|
825
|
+
|
|
826
|
+
const [selections, _setSelections] = useState<
|
|
827
|
+
(ColumnRef | AllColumnsSelector | FunctionCall)[]
|
|
828
|
+
>(searchStatementState?.selections || []);
|
|
829
|
+
const [selectionsUpdateKey, setSelectionsUpdateKey] = useState(0);
|
|
830
|
+
|
|
831
|
+
const [mainDataSource, _setMainDataSource] = useState<DataSource | null>(
|
|
832
|
+
searchStatementState?.from || null
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
const [aggregations, _setAggregations] = useState<AggregationStatement[]>(
|
|
836
|
+
searchStatementState?.aggregations || []
|
|
837
|
+
);
|
|
838
|
+
const [aggregationsUpdateKey, setAggregationsUpdateKey] = useState(0);
|
|
839
|
+
|
|
840
|
+
const [sorting, _setSorting] = useState<SortStatement[]>(
|
|
841
|
+
searchStatementState?.sorting || []
|
|
842
|
+
);
|
|
843
|
+
const [sortingUpdateKey, setSortingUpdateKey] = useState(0);
|
|
844
|
+
|
|
845
|
+
const [offset, _setOffset] = useState<
|
|
846
|
+
SearchStatementLiteralValue | ValueRef | null
|
|
847
|
+
>(searchStatementState?.offset || null);
|
|
848
|
+
const [offsetUpdateKey, setOffsetUpdateKey] = useState(0);
|
|
849
|
+
|
|
850
|
+
const [limit, _setLimit] = useState<
|
|
851
|
+
SearchStatementLiteralValue | ValueRef | null
|
|
852
|
+
>(searchStatementState?.limit || null);
|
|
853
|
+
const [limitUpdateKey, setLimitUpdateKey] = useState(0);
|
|
854
|
+
|
|
855
|
+
const [asList, _setAsList] = useState<boolean>(
|
|
856
|
+
searchStatementState?.asList || true
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
const [hydrationReady, setHydrationReady] = useState(false);
|
|
860
|
+
|
|
861
|
+
const setQueryAST = ({
|
|
862
|
+
ast,
|
|
863
|
+
query
|
|
864
|
+
}: {
|
|
865
|
+
ast: SQLAST | null;
|
|
866
|
+
query: string | null;
|
|
867
|
+
}) => {
|
|
868
|
+
// Don't persist broken intermediate states. `calculateAst` returns
|
|
869
|
+
// `ast: null` when the generated SQL string fails to re-parse — e.g.
|
|
870
|
+
// just after creating an empty nested-search that hasn't been
|
|
871
|
+
// configured yet. Writing that broken string into entity.query would
|
|
872
|
+
// poison it for the next hydration.
|
|
873
|
+
if (!ast) {
|
|
874
|
+
_setSqlAST(null);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (!!entity) {
|
|
879
|
+
const changeSet = project?.logic?.addChangeSet(
|
|
880
|
+
new ChangeSet(
|
|
881
|
+
project?.logic,
|
|
882
|
+
ProjectState.sessionAuthor,
|
|
883
|
+
new Date().toISOString(),
|
|
884
|
+
entity,
|
|
885
|
+
false,
|
|
886
|
+
StateMutationAction.ChangeSearchQuery
|
|
887
|
+
)
|
|
888
|
+
);
|
|
889
|
+
|
|
890
|
+
const sanitizedQuery =
|
|
891
|
+
searchStatementUtils.sanitizeQuery(query) || '';
|
|
892
|
+
|
|
893
|
+
entity?.metaSync({ query: sanitizedQuery }, changeSet);
|
|
894
|
+
|
|
895
|
+
if (
|
|
896
|
+
!!searchStatementState &&
|
|
897
|
+
searchStatementState.isValid &&
|
|
898
|
+
!!ast &&
|
|
899
|
+
!!sanitizedQuery
|
|
900
|
+
) {
|
|
901
|
+
syncSearchEntityOutput(entity, searchStatementState, changeSet);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
project.renderAndCloseChangeSet(changeSet);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
_setSqlAST(ast);
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
useEffect(() => {
|
|
911
|
+
if (!searchStatementState) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (searchStatementState.dataSources) {
|
|
916
|
+
_setDataSources(searchStatementState.dataSources);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const newAst = searchStatementState.calculateAst();
|
|
920
|
+
|
|
921
|
+
if (newAst) {
|
|
922
|
+
Logger.log('query before sanitization: ', entity.query);
|
|
923
|
+
const sanitizedQuery =
|
|
924
|
+
searchStatementUtils.sanitizeQuery(entity.query) || '';
|
|
925
|
+
Logger.log('sanitizedQuery: ', sanitizedQuery);
|
|
926
|
+
if (
|
|
927
|
+
(!!newAst.query && !sanitizedQuery) ||
|
|
928
|
+
(!newAst.query && !!sanitizedQuery) ||
|
|
929
|
+
(!!newAst.query &&
|
|
930
|
+
!!sanitizedQuery &&
|
|
931
|
+
newAst.query !== sanitizedQuery)
|
|
932
|
+
) {
|
|
933
|
+
Logger.log('--- Setting query ---');
|
|
934
|
+
Logger.log('newAst.query: ', newAst.query);
|
|
935
|
+
Logger.log('entity query: ', sanitizedQuery);
|
|
936
|
+
setQueryAST(newAst);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (!!searchStatementState.from) {
|
|
941
|
+
_setMainDataSource(searchStatementState.from);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
_setWhere(searchStatementState.where);
|
|
945
|
+
_setOffset(searchStatementState.offset);
|
|
946
|
+
setOffsetUpdateKey((key) => key + 1);
|
|
947
|
+
_setLimit(searchStatementState.limit);
|
|
948
|
+
setLimitUpdateKey((key) => key + 1);
|
|
949
|
+
_setAsList(searchStatementState.asList);
|
|
950
|
+
_setAggregations(searchStatementState.aggregations);
|
|
951
|
+
_setSelections(searchStatementState.selections);
|
|
952
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
953
|
+
_setSorting(searchStatementState.sorting);
|
|
954
|
+
|
|
955
|
+
setHydrationReady(true);
|
|
956
|
+
}, [searchStatementState]);
|
|
957
|
+
|
|
958
|
+
const removeAggregation = (value: AggregationStatement) => {
|
|
959
|
+
searchStatementState.removeAggregation(value);
|
|
960
|
+
_setAggregations(searchStatementState.aggregations);
|
|
961
|
+
_setSelections(searchStatementState.selections);
|
|
962
|
+
_setSorting(searchStatementState.sorting);
|
|
963
|
+
_setWhere(searchStatementState.where);
|
|
964
|
+
setAggregationsUpdateKey((key) => key + 1);
|
|
965
|
+
setDataSourcesUpdateKey((key) => key + 1);
|
|
966
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
967
|
+
setSortingUpdateKey((key) => key + 1);
|
|
968
|
+
setWhereUpdateKey((key) => key + 1);
|
|
969
|
+
|
|
970
|
+
const newAst = searchStatementState.calculateAst();
|
|
971
|
+
setQueryAST(newAst);
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
const addAggregation = (value: AggregationStatement) => {
|
|
975
|
+
searchStatementState.addAggregation(value);
|
|
976
|
+
_setAggregations(searchStatementState.aggregations);
|
|
977
|
+
setAggregationsUpdateKey((key) => key + 1);
|
|
978
|
+
setDataSourcesUpdateKey((key) => key + 1);
|
|
979
|
+
|
|
980
|
+
const newAst = searchStatementState.calculateAst();
|
|
981
|
+
setQueryAST(newAst);
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
const editAggregation = (value: AggregationStatement) => {
|
|
985
|
+
searchStatementState.editAggregation(value);
|
|
986
|
+
_setAggregations(searchStatementState.aggregations);
|
|
987
|
+
setAggregationsUpdateKey((key) => key + 1);
|
|
988
|
+
setDataSourcesUpdateKey((key) => key + 1);
|
|
989
|
+
|
|
990
|
+
const newAst = searchStatementState.calculateAst();
|
|
991
|
+
setQueryAST(newAst);
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
const addSelection = (
|
|
995
|
+
value: ColumnRef | AllColumnsSelector | FunctionCall
|
|
996
|
+
) => {
|
|
997
|
+
searchStatementState.addSelection(value);
|
|
998
|
+
_setSelections(searchStatementState.selections);
|
|
999
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
1000
|
+
|
|
1001
|
+
const newAst = searchStatementState.calculateAst();
|
|
1002
|
+
setQueryAST(newAst);
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
const editSelection = (
|
|
1006
|
+
value: ColumnRef | AllColumnsSelector | FunctionCall
|
|
1007
|
+
) => {
|
|
1008
|
+
searchStatementState.editSelection(value);
|
|
1009
|
+
_setSelections(searchStatementState.selections);
|
|
1010
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
1011
|
+
|
|
1012
|
+
const newAst = searchStatementState.calculateAst();
|
|
1013
|
+
setQueryAST(newAst);
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
const removeSelection = (
|
|
1017
|
+
value: ColumnRef | AllColumnsSelector | FunctionCall
|
|
1018
|
+
) => {
|
|
1019
|
+
searchStatementState.removeSelection(value);
|
|
1020
|
+
_setSelections(searchStatementState.selections);
|
|
1021
|
+
_setSorting(searchStatementState.sorting);
|
|
1022
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
1023
|
+
setSortingUpdateKey((key) => key + 1);
|
|
1024
|
+
|
|
1025
|
+
const newAst = searchStatementState.calculateAst();
|
|
1026
|
+
setQueryAST(newAst);
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
const addSorting = (value: SortStatement) => {
|
|
1030
|
+
searchStatementState.addSort(value);
|
|
1031
|
+
_setSorting(searchStatementState.sorting);
|
|
1032
|
+
|
|
1033
|
+
const newAst = searchStatementState.calculateAst();
|
|
1034
|
+
setQueryAST(newAst);
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
const editSorting = (value: SortStatement) => {
|
|
1038
|
+
searchStatementState.editSort(value);
|
|
1039
|
+
_setSorting(searchStatementState.sorting);
|
|
1040
|
+
|
|
1041
|
+
const newAst = searchStatementState.calculateAst();
|
|
1042
|
+
setQueryAST(newAst);
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1045
|
+
const removeSorting = (value: SortStatement) => {
|
|
1046
|
+
searchStatementState.removeSort(value);
|
|
1047
|
+
_setSorting(searchStatementState.sorting);
|
|
1048
|
+
setSortingUpdateKey((key) => key + 1);
|
|
1049
|
+
|
|
1050
|
+
const newAst = searchStatementState.calculateAst();
|
|
1051
|
+
setQueryAST(newAst);
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
const setMainDataSource = (value: DataSource | null) => {
|
|
1055
|
+
searchStatementState.setFrom(value);
|
|
1056
|
+
_setMainDataSource(value);
|
|
1057
|
+
_setSelections(searchStatementState.selections);
|
|
1058
|
+
_setSorting(searchStatementState.sorting);
|
|
1059
|
+
_setAggregations(searchStatementState.aggregations);
|
|
1060
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
1061
|
+
setSortingUpdateKey((key) => key + 1);
|
|
1062
|
+
setAggregationsUpdateKey((key) => key + 1);
|
|
1063
|
+
|
|
1064
|
+
const newAst = searchStatementState.calculateAst();
|
|
1065
|
+
setQueryAST(newAst);
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
const setWhere = (value: WhereStatement | null) => {
|
|
1069
|
+
searchStatementState.where = value;
|
|
1070
|
+
|
|
1071
|
+
_setWhere(value);
|
|
1072
|
+
setWhereUpdateKey((key) => key + 1);
|
|
1073
|
+
|
|
1074
|
+
const newAst = searchStatementState.calculateAst();
|
|
1075
|
+
setQueryAST(newAst);
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
const setOffset = (
|
|
1079
|
+
value: SearchStatementLiteralValue | ValueRef | null
|
|
1080
|
+
) => {
|
|
1081
|
+
searchStatementState.offset = value;
|
|
1082
|
+
_setOffset(value);
|
|
1083
|
+
setOffsetUpdateKey((key) => key + 1);
|
|
1084
|
+
|
|
1085
|
+
const newAst = searchStatementState.calculateAst();
|
|
1086
|
+
setQueryAST(newAst);
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
const setLimit = (value: SearchStatementLiteralValue | ValueRef | null) => {
|
|
1090
|
+
searchStatementState.limit = value;
|
|
1091
|
+
_setLimit(value);
|
|
1092
|
+
setLimitUpdateKey((key) => key + 1);
|
|
1093
|
+
|
|
1094
|
+
if (
|
|
1095
|
+
value?.type === SearchStatementNodeType.LiteralValue &&
|
|
1096
|
+
value.valueType === SearchLiteralValueType.Number &&
|
|
1097
|
+
(value.value as number) > 1
|
|
1098
|
+
) {
|
|
1099
|
+
searchStatementState.asList = true;
|
|
1100
|
+
|
|
1101
|
+
_setAsList(true);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const newAst = searchStatementState.calculateAst();
|
|
1105
|
+
setQueryAST(newAst);
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
const setAsList = (value: boolean) => {
|
|
1109
|
+
searchStatementState.asList = value;
|
|
1110
|
+
|
|
1111
|
+
_setAsList(value);
|
|
1112
|
+
|
|
1113
|
+
if (!value) {
|
|
1114
|
+
const newLimitLiteral = new SearchStatementLiteralValue(
|
|
1115
|
+
searchStatementState
|
|
1116
|
+
);
|
|
1117
|
+
newLimitLiteral.value = 1;
|
|
1118
|
+
newLimitLiteral.valueType = SearchLiteralValueType.Number;
|
|
1119
|
+
|
|
1120
|
+
searchStatementState.limit = newLimitLiteral;
|
|
1121
|
+
_setLimit(newLimitLiteral);
|
|
1122
|
+
setLimitUpdateKey((key) => key + 1);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
const newAst = searchStatementState.calculateAst();
|
|
1126
|
+
setQueryAST(newAst);
|
|
1127
|
+
};
|
|
1128
|
+
|
|
1129
|
+
const addDatasource = (dataSource: DataSource) => {
|
|
1130
|
+
const storedDataSource = searchStatementState.addDataSource(dataSource);
|
|
1131
|
+
|
|
1132
|
+
if (dataSource !== storedDataSource) {
|
|
1133
|
+
// Add the data source to the list of data sources available to use throughout the search statement builder
|
|
1134
|
+
_setDataSources((dataSources) => [...dataSources, dataSource]);
|
|
1135
|
+
setDataSourcesUpdateKey((key) => key + 1);
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
// Based on the main data source, we resolve the database client
|
|
1140
|
+
const resolveDatabaseClient = (): Database | null => {
|
|
1141
|
+
Logger.log('resolveDatabaseClient');
|
|
1142
|
+
if (!!searchStatementState?.from) {
|
|
1143
|
+
const mainDataSourceEntity =
|
|
1144
|
+
searchStatementState?.mainPersistedEntity || null;
|
|
1145
|
+
|
|
1146
|
+
if (!mainDataSourceEntity) {
|
|
1147
|
+
Logger.log('No main data source entity');
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
const database =
|
|
1152
|
+
project?.localDatabaseStore?.getDatabaseForPersistedEntity(
|
|
1153
|
+
mainDataSourceEntity
|
|
1154
|
+
);
|
|
1155
|
+
|
|
1156
|
+
Logger.log('Database', database);
|
|
1157
|
+
|
|
1158
|
+
return database;
|
|
1159
|
+
} else {
|
|
1160
|
+
Logger.log('Selecting first database');
|
|
1161
|
+
// There is no need for any table to be present, so we just need ANY client, to query against
|
|
1162
|
+
return project?.localDatabaseStore?.databases[0] || null;
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
if (!searchStatementState || !entity || !project) {
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
return (
|
|
1171
|
+
<SearchStatementContext.Provider
|
|
1172
|
+
value={{
|
|
1173
|
+
tabManager,
|
|
1174
|
+
searchStatementState,
|
|
1175
|
+
dataSources,
|
|
1176
|
+
mainDataSource,
|
|
1177
|
+
setMainDataSource,
|
|
1178
|
+
aggregations,
|
|
1179
|
+
aggregationsUpdateKey,
|
|
1180
|
+
addAggregation,
|
|
1181
|
+
removeAggregation,
|
|
1182
|
+
editAggregation,
|
|
1183
|
+
selections,
|
|
1184
|
+
selectionsUpdateKey,
|
|
1185
|
+
addSelection,
|
|
1186
|
+
editSelection,
|
|
1187
|
+
removeSelection,
|
|
1188
|
+
sorting,
|
|
1189
|
+
sortingUpdateKey,
|
|
1190
|
+
addSorting,
|
|
1191
|
+
editSorting,
|
|
1192
|
+
removeSorting,
|
|
1193
|
+
setOffset,
|
|
1194
|
+
setLimit,
|
|
1195
|
+
setAsList,
|
|
1196
|
+
setWhere,
|
|
1197
|
+
resolveDatabaseClient,
|
|
1198
|
+
where,
|
|
1199
|
+
whereUpdateKey,
|
|
1200
|
+
offset,
|
|
1201
|
+
asList,
|
|
1202
|
+
limit,
|
|
1203
|
+
project,
|
|
1204
|
+
entity,
|
|
1205
|
+
sqlAST,
|
|
1206
|
+
offsetUpdateKey,
|
|
1207
|
+
limitUpdateKey,
|
|
1208
|
+
dataSourcesUpdateKey,
|
|
1209
|
+
hydrationReady,
|
|
1210
|
+
isNested: false
|
|
1211
|
+
}}
|
|
1212
|
+
>
|
|
1213
|
+
{children}
|
|
1214
|
+
</SearchStatementContext.Provider>
|
|
1215
|
+
);
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
export const withSearchStatementProvider = <
|
|
1219
|
+
T extends {
|
|
1220
|
+
project: EditorService;
|
|
1221
|
+
ready: boolean;
|
|
1222
|
+
tabManager: ITabManager;
|
|
1223
|
+
} = {
|
|
1224
|
+
project: EditorService;
|
|
1225
|
+
ready: boolean;
|
|
1226
|
+
tabManager: ITabManager;
|
|
1227
|
+
}
|
|
1228
|
+
>(
|
|
1229
|
+
Component: React.ComponentType<T>
|
|
1230
|
+
) => {
|
|
1231
|
+
return function WrappedComponent(props: T) {
|
|
1232
|
+
return (
|
|
1233
|
+
<SearchStatementProvider
|
|
1234
|
+
project={props.project}
|
|
1235
|
+
ready={props.ready}
|
|
1236
|
+
tabManager={props.tabManager}
|
|
1237
|
+
>
|
|
1238
|
+
<Component {...props} />
|
|
1239
|
+
</SearchStatementProvider>
|
|
1240
|
+
);
|
|
1241
|
+
};
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
const NestedSearchStatementProvider = ({
|
|
1245
|
+
children,
|
|
1246
|
+
project,
|
|
1247
|
+
ready,
|
|
1248
|
+
tabManager,
|
|
1249
|
+
parent,
|
|
1250
|
+
// recalculateParent,
|
|
1251
|
+
query
|
|
1252
|
+
}: {
|
|
1253
|
+
children: ReactNode;
|
|
1254
|
+
project: EditorService;
|
|
1255
|
+
ready: boolean;
|
|
1256
|
+
tabManager: ITabManager;
|
|
1257
|
+
parent: SearchStatementState;
|
|
1258
|
+
// recalculateParent: () => void;
|
|
1259
|
+
query: SearchStatementState;
|
|
1260
|
+
}) => {
|
|
1261
|
+
const { entityId } = useParams<{
|
|
1262
|
+
entityType: EntityType;
|
|
1263
|
+
entityId: EntityId;
|
|
1264
|
+
}>();
|
|
1265
|
+
|
|
1266
|
+
const entity: SearchState | null = project?.logic.get(
|
|
1267
|
+
entityId
|
|
1268
|
+
) as SearchState | null;
|
|
1269
|
+
|
|
1270
|
+
const [sqlAST, _setSqlAST] = useState<SQLAST | null>(null);
|
|
1271
|
+
|
|
1272
|
+
const searchStatementState: SearchStatementState | null = query;
|
|
1273
|
+
|
|
1274
|
+
const [dataSources, _setDataSources] = useState<DataSource[]>(
|
|
1275
|
+
searchStatementState?.dataSources || []
|
|
1276
|
+
);
|
|
1277
|
+
const [dataSourcesUpdateKey, setDataSourcesUpdateKey] = useState(0);
|
|
1278
|
+
|
|
1279
|
+
const [where, _setWhere] = useState<WhereStatement | null>(
|
|
1280
|
+
searchStatementState?.where || null
|
|
1281
|
+
);
|
|
1282
|
+
const [whereUpdateKey, setWhereUpdateKey] = useState(0);
|
|
1283
|
+
|
|
1284
|
+
const [selections, _setSelections] = useState<
|
|
1285
|
+
(ColumnRef | AllColumnsSelector | FunctionCall)[]
|
|
1286
|
+
>(searchStatementState?.selections || []);
|
|
1287
|
+
const [selectionsUpdateKey, setSelectionsUpdateKey] = useState(0);
|
|
1288
|
+
|
|
1289
|
+
const [mainDataSource, _setMainDataSource] = useState<DataSource | null>(
|
|
1290
|
+
searchStatementState?.from || null
|
|
1291
|
+
);
|
|
1292
|
+
|
|
1293
|
+
const [aggregations, _setAggregations] = useState<AggregationStatement[]>(
|
|
1294
|
+
searchStatementState?.aggregations || []
|
|
1295
|
+
);
|
|
1296
|
+
const [aggregationsUpdateKey, setAggregationsUpdateKey] = useState(0);
|
|
1297
|
+
|
|
1298
|
+
const [sorting, _setSorting] = useState<SortStatement[]>(
|
|
1299
|
+
searchStatementState?.sorting || []
|
|
1300
|
+
);
|
|
1301
|
+
const [sortingUpdateKey, setSortingUpdateKey] = useState(0);
|
|
1302
|
+
|
|
1303
|
+
const [offset, _setOffset] = useState<
|
|
1304
|
+
SearchStatementLiteralValue | ValueRef | null
|
|
1305
|
+
>(searchStatementState?.offset || null);
|
|
1306
|
+
const [offsetUpdateKey, setOffsetUpdateKey] = useState(0);
|
|
1307
|
+
|
|
1308
|
+
const [limit, _setLimit] = useState<
|
|
1309
|
+
SearchStatementLiteralValue | ValueRef | null
|
|
1310
|
+
>(searchStatementState?.limit || null);
|
|
1311
|
+
const [limitUpdateKey, setLimitUpdateKey] = useState(0);
|
|
1312
|
+
|
|
1313
|
+
const [hydrationReady, setHydrationReady] = useState(false);
|
|
1314
|
+
|
|
1315
|
+
const setQueryAST = ({
|
|
1316
|
+
ast,
|
|
1317
|
+
query
|
|
1318
|
+
}: {
|
|
1319
|
+
ast: SQLAST | null;
|
|
1320
|
+
query: string | null;
|
|
1321
|
+
}) => {
|
|
1322
|
+
// We don't set it in the entity becuase it's a nested search statement
|
|
1323
|
+
// if (!!entity) {
|
|
1324
|
+
// entity.query = query;
|
|
1325
|
+
// }
|
|
1326
|
+
// if (recalculateParent) {
|
|
1327
|
+
// recalculateParent();
|
|
1328
|
+
// }
|
|
1329
|
+
|
|
1330
|
+
_setSqlAST(ast);
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
useEffect(() => {
|
|
1334
|
+
if (!searchStatementState) {
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
if (searchStatementState.dataSources) {
|
|
1339
|
+
_setDataSources(searchStatementState.dataSources);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
const newAst = searchStatementState.calculateAst();
|
|
1343
|
+
|
|
1344
|
+
if (newAst) {
|
|
1345
|
+
setQueryAST(newAst);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (!!searchStatementState.from) {
|
|
1349
|
+
_setMainDataSource(searchStatementState.from);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
_setWhere(searchStatementState.where);
|
|
1353
|
+
_setOffset(searchStatementState.offset);
|
|
1354
|
+
setOffsetUpdateKey((key) => key + 1);
|
|
1355
|
+
_setLimit(searchStatementState.limit);
|
|
1356
|
+
setLimitUpdateKey((key) => key + 1);
|
|
1357
|
+
_setAggregations(searchStatementState.aggregations);
|
|
1358
|
+
_setSelections(searchStatementState.selections);
|
|
1359
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
1360
|
+
_setSorting(searchStatementState.sorting);
|
|
1361
|
+
|
|
1362
|
+
setHydrationReady(true);
|
|
1363
|
+
}, [searchStatementState]);
|
|
1364
|
+
|
|
1365
|
+
const removeAggregation = (value: AggregationStatement) => {
|
|
1366
|
+
searchStatementState.removeAggregation(value);
|
|
1367
|
+
_setAggregations(searchStatementState.aggregations);
|
|
1368
|
+
_setSelections(searchStatementState.selections);
|
|
1369
|
+
_setSorting(searchStatementState.sorting);
|
|
1370
|
+
_setWhere(searchStatementState.where);
|
|
1371
|
+
setAggregationsUpdateKey((key) => key + 1);
|
|
1372
|
+
setDataSourcesUpdateKey((key) => key + 1);
|
|
1373
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
1374
|
+
setSortingUpdateKey((key) => key + 1);
|
|
1375
|
+
setWhereUpdateKey((key) => key + 1);
|
|
1376
|
+
|
|
1377
|
+
const newAst = searchStatementState.calculateAst();
|
|
1378
|
+
setQueryAST(newAst);
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
const addAggregation = (value: AggregationStatement) => {
|
|
1382
|
+
searchStatementState.addAggregation(value);
|
|
1383
|
+
_setAggregations(searchStatementState.aggregations);
|
|
1384
|
+
setAggregationsUpdateKey((key) => key + 1);
|
|
1385
|
+
setDataSourcesUpdateKey((key) => key + 1);
|
|
1386
|
+
|
|
1387
|
+
const newAst = searchStatementState.calculateAst();
|
|
1388
|
+
setQueryAST(newAst);
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1391
|
+
const editAggregation = (value: AggregationStatement) => {
|
|
1392
|
+
searchStatementState.editAggregation(value);
|
|
1393
|
+
_setAggregations(searchStatementState.aggregations);
|
|
1394
|
+
setAggregationsUpdateKey((key) => key + 1);
|
|
1395
|
+
setDataSourcesUpdateKey((key) => key + 1);
|
|
1396
|
+
|
|
1397
|
+
const newAst = searchStatementState.calculateAst();
|
|
1398
|
+
setQueryAST(newAst);
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
const addSelection = (
|
|
1402
|
+
value: ColumnRef | AllColumnsSelector | FunctionCall
|
|
1403
|
+
) => {
|
|
1404
|
+
searchStatementState.addSelection(value);
|
|
1405
|
+
_setSelections(searchStatementState.selections);
|
|
1406
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
1407
|
+
|
|
1408
|
+
const newAst = searchStatementState.calculateAst();
|
|
1409
|
+
setQueryAST(newAst);
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
const editSelection = (
|
|
1413
|
+
value: ColumnRef | AllColumnsSelector | FunctionCall
|
|
1414
|
+
) => {
|
|
1415
|
+
searchStatementState.editSelection(value);
|
|
1416
|
+
_setSelections(searchStatementState.selections);
|
|
1417
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
1418
|
+
|
|
1419
|
+
const newAst = searchStatementState.calculateAst();
|
|
1420
|
+
setQueryAST(newAst);
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
const removeSelection = (
|
|
1424
|
+
value: ColumnRef | AllColumnsSelector | FunctionCall
|
|
1425
|
+
) => {
|
|
1426
|
+
searchStatementState.removeSelection(value);
|
|
1427
|
+
_setSelections(searchStatementState.selections);
|
|
1428
|
+
_setSorting(searchStatementState.sorting);
|
|
1429
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
1430
|
+
setSortingUpdateKey((key) => key + 1);
|
|
1431
|
+
|
|
1432
|
+
const newAst = searchStatementState.calculateAst();
|
|
1433
|
+
setQueryAST(newAst);
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
const addSorting = (value: SortStatement) => {
|
|
1437
|
+
searchStatementState.addSort(value);
|
|
1438
|
+
_setSorting(searchStatementState.sorting);
|
|
1439
|
+
setSortingUpdateKey((key) => key + 1);
|
|
1440
|
+
|
|
1441
|
+
const newAst = searchStatementState.calculateAst();
|
|
1442
|
+
setQueryAST(newAst);
|
|
1443
|
+
};
|
|
1444
|
+
|
|
1445
|
+
const editSorting = (value: SortStatement) => {
|
|
1446
|
+
searchStatementState.editSort(value);
|
|
1447
|
+
_setSorting(searchStatementState.sorting);
|
|
1448
|
+
|
|
1449
|
+
const newAst = searchStatementState.calculateAst();
|
|
1450
|
+
setQueryAST(newAst);
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1453
|
+
const removeSorting = (value: SortStatement) => {
|
|
1454
|
+
searchStatementState.removeSort(value);
|
|
1455
|
+
_setSorting(searchStatementState.sorting);
|
|
1456
|
+
setSortingUpdateKey((key) => key + 1);
|
|
1457
|
+
|
|
1458
|
+
const newAst = searchStatementState.calculateAst();
|
|
1459
|
+
setQueryAST(newAst);
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
const setMainDataSource = (value: DataSource | null) => {
|
|
1463
|
+
searchStatementState.setFrom(value);
|
|
1464
|
+
_setMainDataSource(value);
|
|
1465
|
+
_setSelections(searchStatementState.selections);
|
|
1466
|
+
_setSorting(searchStatementState.sorting);
|
|
1467
|
+
_setAggregations(searchStatementState.aggregations);
|
|
1468
|
+
setSelectionsUpdateKey((key) => key + 1);
|
|
1469
|
+
setSortingUpdateKey((key) => key + 1);
|
|
1470
|
+
setAggregationsUpdateKey((key) => key + 1);
|
|
1471
|
+
|
|
1472
|
+
const newAst = searchStatementState.calculateAst();
|
|
1473
|
+
setQueryAST(newAst);
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
const setWhere = (value: WhereStatement | null) => {
|
|
1477
|
+
searchStatementState.where = value;
|
|
1478
|
+
_setWhere(value);
|
|
1479
|
+
setWhereUpdateKey((key) => key + 1);
|
|
1480
|
+
|
|
1481
|
+
const newAst = searchStatementState.calculateAst();
|
|
1482
|
+
setQueryAST(newAst);
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
const setOffset = (
|
|
1486
|
+
value: SearchStatementLiteralValue | ValueRef | null
|
|
1487
|
+
) => {
|
|
1488
|
+
searchStatementState.offset = value;
|
|
1489
|
+
_setOffset(value);
|
|
1490
|
+
setOffsetUpdateKey((key) => key + 1);
|
|
1491
|
+
|
|
1492
|
+
const newAst = searchStatementState.calculateAst();
|
|
1493
|
+
setQueryAST(newAst);
|
|
1494
|
+
};
|
|
1495
|
+
|
|
1496
|
+
const setLimit = (value: SearchStatementLiteralValue | ValueRef | null) => {
|
|
1497
|
+
searchStatementState.limit = value;
|
|
1498
|
+
_setLimit(value);
|
|
1499
|
+
setLimitUpdateKey((key) => key + 1);
|
|
1500
|
+
|
|
1501
|
+
if (
|
|
1502
|
+
value?.type === SearchStatementNodeType.LiteralValue &&
|
|
1503
|
+
value.valueType === SearchLiteralValueType.Number &&
|
|
1504
|
+
(value.value as number) > 1
|
|
1505
|
+
) {
|
|
1506
|
+
searchStatementState.asList = true;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
const newAst = searchStatementState.calculateAst();
|
|
1510
|
+
setQueryAST(newAst);
|
|
1511
|
+
};
|
|
1512
|
+
|
|
1513
|
+
const addDatasource = (dataSource: DataSource) => {
|
|
1514
|
+
const storedDataSource = searchStatementState.addDataSource(dataSource);
|
|
1515
|
+
|
|
1516
|
+
if (dataSource !== storedDataSource) {
|
|
1517
|
+
// Add the data source to the list of data sources available to use throughout the search statement builder
|
|
1518
|
+
_setDataSources((dataSources) => [...dataSources, dataSource]);
|
|
1519
|
+
setDataSourcesUpdateKey((key) => key + 1);
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
// Based on the main data source, we resolve the database client
|
|
1524
|
+
const resolveDatabaseClient = (): Database | null => {
|
|
1525
|
+
if (!!searchStatementState?.from) {
|
|
1526
|
+
const mainDataSourceEntity =
|
|
1527
|
+
searchStatementState?.mainPersistedEntity || null;
|
|
1528
|
+
|
|
1529
|
+
if (!mainDataSourceEntity) {
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
const database =
|
|
1534
|
+
project?.localDatabaseStore?.getDatabaseForPersistedEntity(
|
|
1535
|
+
mainDataSourceEntity
|
|
1536
|
+
);
|
|
1537
|
+
|
|
1538
|
+
return database;
|
|
1539
|
+
} else {
|
|
1540
|
+
// There is no need for any table to be present, so we just need ANY client, to query against
|
|
1541
|
+
return project?.localDatabaseStore?.databases[0] || null;
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
|
|
1545
|
+
if (!searchStatementState || !entity || !project || !ready) {
|
|
1546
|
+
return null;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
return (
|
|
1550
|
+
<SearchStatementContext.Provider
|
|
1551
|
+
value={{
|
|
1552
|
+
tabManager,
|
|
1553
|
+
searchStatementState,
|
|
1554
|
+
dataSources,
|
|
1555
|
+
mainDataSource,
|
|
1556
|
+
setMainDataSource,
|
|
1557
|
+
aggregations,
|
|
1558
|
+
aggregationsUpdateKey,
|
|
1559
|
+
addAggregation,
|
|
1560
|
+
removeAggregation,
|
|
1561
|
+
editAggregation,
|
|
1562
|
+
selections,
|
|
1563
|
+
selectionsUpdateKey,
|
|
1564
|
+
addSelection,
|
|
1565
|
+
editSelection,
|
|
1566
|
+
removeSelection,
|
|
1567
|
+
sorting,
|
|
1568
|
+
sortingUpdateKey,
|
|
1569
|
+
addSorting,
|
|
1570
|
+
editSorting,
|
|
1571
|
+
removeSorting,
|
|
1572
|
+
setOffset,
|
|
1573
|
+
setLimit,
|
|
1574
|
+
setAsList: () => {},
|
|
1575
|
+
setWhere,
|
|
1576
|
+
resolveDatabaseClient,
|
|
1577
|
+
where,
|
|
1578
|
+
whereUpdateKey,
|
|
1579
|
+
offset,
|
|
1580
|
+
asList: true,
|
|
1581
|
+
limit,
|
|
1582
|
+
project,
|
|
1583
|
+
entity,
|
|
1584
|
+
sqlAST,
|
|
1585
|
+
offsetUpdateKey,
|
|
1586
|
+
limitUpdateKey,
|
|
1587
|
+
dataSourcesUpdateKey,
|
|
1588
|
+
hydrationReady,
|
|
1589
|
+
isNested: true
|
|
1590
|
+
}}
|
|
1591
|
+
>
|
|
1592
|
+
{children}
|
|
1593
|
+
</SearchStatementContext.Provider>
|
|
1594
|
+
);
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
export const withNestedSearchStatementProvider = <
|
|
1598
|
+
T extends {
|
|
1599
|
+
project: EditorService;
|
|
1600
|
+
ready: boolean;
|
|
1601
|
+
parent: SearchStatementState;
|
|
1602
|
+
query: SearchStatementState;
|
|
1603
|
+
tabManager: ITabManager;
|
|
1604
|
+
// recalculateParent: () => void;
|
|
1605
|
+
} = {
|
|
1606
|
+
project: EditorService;
|
|
1607
|
+
ready: boolean;
|
|
1608
|
+
parent: SearchStatementState;
|
|
1609
|
+
query: SearchStatementState;
|
|
1610
|
+
tabManager: ITabManager;
|
|
1611
|
+
// recalculateParent: () => void;
|
|
1612
|
+
}
|
|
1613
|
+
>(
|
|
1614
|
+
Component: React.ComponentType<T>
|
|
1615
|
+
) => {
|
|
1616
|
+
return function WrappedComponent(props: T) {
|
|
1617
|
+
return (
|
|
1618
|
+
<NestedSearchStatementProvider
|
|
1619
|
+
project={props.project}
|
|
1620
|
+
ready={props.ready}
|
|
1621
|
+
tabManager={props.tabManager}
|
|
1622
|
+
parent={props.parent}
|
|
1623
|
+
query={props.query}
|
|
1624
|
+
// recalculateParent={props.recalculateParent}
|
|
1625
|
+
>
|
|
1626
|
+
<Component {...props} />
|
|
1627
|
+
</NestedSearchStatementProvider>
|
|
1628
|
+
);
|
|
1629
|
+
};
|
|
1630
|
+
};
|