/** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Dialog, PanelLoadingIndicator, BlankPanelContent, ControlledDropdownMenu, MenuContent, MenuContentItem, MenuIcon, MenuContentDivider, Modal, ModalHeader, ModalBody, ModalFooter, ModalTitle, ModalFooterButton, ExclamationTriangleIcon, clsx, ModalHeaderActions, TimesIcon, Panel, PanelForm, PanelFormBooleanField, PanelFormTextField, PanelFullContent, CustomSelectorInput, PencilIcon, MoonIcon, SunIcon, SparkleIcon, } from '@finos/legend-art'; import { observer } from 'mobx-react-lite'; import { useEffect, useMemo, useRef, useState } from 'react'; import { type MappingQueryCreatorPathParams, type ExistingQueryEditorPathParams, type ServiceQueryCreatorPathParams, LEGEND_QUERY_QUERY_PARAM_TOKEN, LEGEND_QUERY_ROUTE_PATTERN_TOKEN, generateQuerySetupRoute, generateExistingQueryEditorRoute, } from '../__lib__/LegendQueryNavigation.js'; import { ExistingQueryEditorStore } from '../stores/QueryEditorStore.js'; import { LegendQueryTelemetryHelper } from '../__lib__/LegendQueryTelemetryHelper.js'; import { LEGEND_APPLICATION_COLOR_THEME, ReleaseLogManager, ReleaseNotesManager, useApplicationStore, } from '@finos/legend-application'; import { useParams } from '@finos/legend-application/browser'; import { MappingQueryCreatorStoreProvider, ExistingQueryEditorStoreProvider, ServiceQueryCreatorStoreProvider, useQueryEditorStore, } from './QueryEditorStoreProvider.js'; import { flowResult } from 'mobx'; import { QueryBuilder, QueryBuilderNavigationBlocker, QueryLoaderDialog, QueryBuilderDiffViewPanel, DataProductQueryBuilderState, type QueryBuilderState, } from '@finos/legend-query-builder'; import { generateGAVCoordinates } from '@finos/legend-storage'; import { type Query, QueryDataSpaceExecutionContext, QueryExplicitExecutionContext, } from '@finos/legend-graph'; import { LATEST_VERSION_ALIAS } from '@finos/legend-server-depot'; import { buildVersionOption, type VersionOption } from './QuerySetup.js'; import { QueryEditorExistingQueryVersionRevertModal } from './QueryEdtiorExistingQueryVersionRevertModal.js'; import { debounce, compareSemVerVersions, guaranteeNonNullable, isValidUrl, } from '@finos/legend-shared'; import { LegendQueryInfo } from './LegendQueryAppInfo.js'; import { QueryEditorDataspaceInfoModal } from './data-space/DataSpaceInfo.js'; import { QueryEditorDataProductInfoModal } from './data-product/DataProductInfo.js'; import { QueryEditorIngestInfoModal } from './ingest/IngestInfo.js'; import { IngestLegendQueryBuilderState } from '../stores/ingest/IngestLegendQueryBuilderState.js'; import { DataSpaceQueryBuilderState } from '@finos/legend-extension-dsl-data-space/application'; import { LegendQueryBareQueryBuilderState } from '../stores/data-space/LegendQueryBareQueryBuilderState.js'; import { extractQueryParams } from './utils/QueryParameterUtils.js'; import type { QueryTitleDescriptionAISuggestionRequest } from '../stores/LegendQueryApplicationPlugin.js'; const buildAISuggestionRequest = async ( queryBuilderState: QueryBuilderState, name?: string, ): Promise => { const graphManager = queryBuilderState.graphManagerState.graphManager; const lambda = queryBuilderState.buildQuery(); const content = await graphManager.lambdaToPureCode(lambda); const request: QueryTitleDescriptionAISuggestionRequest = { content }; const execContext = queryBuilderState.getQueryExecutionContext(); if (execContext instanceof QueryDataSpaceExecutionContext) { request.executionContext = { dataSpacePath: execContext.dataSpacePath, executionKey: execContext.executionKey, }; } else if (execContext instanceof QueryExplicitExecutionContext) { request.executionContext = { mapping: execContext.mapping.value.path, runtime: execContext.runtime.value.path, }; } const parameterValues = queryBuilderState.getCurrentParameterValues(); if (parameterValues && parameterValues.size > 0) { request.defaultParameterValues = []; for (const [paramName, valueSpec] of parameterValues) { const serialized = graphManager.serializeValueSpecification(valueSpec); const paramContent = await graphManager.valueSpecificationToPureCode(serialized); request.defaultParameterValues.push({ name: paramName, content: paramContent, }); } } if (name) { request.name = name; } return request; }; const CreateQueryDialog = observer(() => { const editorStore = useQueryEditorStore(); const createQueryState = editorStore.queryCreatorState; const close = (): void => createQueryState.close(); const applicationStore = useApplicationStore(); const create = (): void => { flowResult(createQueryState.createQuery()).catch( applicationStore.alertUnhandledError, ); }; const isExistingQueryName = createQueryState.editorStore.existingQueryName; const description = createQueryState.queryDescription; const isEmptyName = !createQueryState.queryName; const isDescriptionEmptyOrValid = !description || /[a-zA-Z0-9]/.test(description); const descriptionInputRef = useRef(null); const changeDescription: React.ChangeEventHandler = ( event, ) => { createQueryState.setQueryDescription(event.target.value); }; const nameInputRef = useRef(null); const legendAIUrl = editorStore.applicationStore.config.legendAIUrl; const aiSuggester = legendAIUrl ? editorStore.pluginManager .getApplicationPlugins() .map((p) => p.getExtraQueryTitleDescriptionAISuggester?.()) .find(Boolean) : undefined; const [isSuggestingWithAI, setIsSuggestingWithAI] = useState(false); const [aiSuggestion, setAISuggestion] = useState< { title: string; description: string } | undefined >(undefined); const suggestWithAI = async (): Promise => { if (!aiSuggester || !editorStore.queryBuilderState || !legendAIUrl) { return; } LegendQueryTelemetryHelper.logEvent_QueryAISuggestLaunched( applicationStore.telemetryService, ); setIsSuggestingWithAI(true); setAISuggestion(undefined); try { const request = await buildAISuggestionRequest( editorStore.queryBuilderState, ); const suggestion = await aiSuggester(request, legendAIUrl); setAISuggestion(suggestion); } finally { setIsSuggestingWithAI(false); } }; const acceptAISuggestion = (): void => { if (!aiSuggestion) { return; } LegendQueryTelemetryHelper.logEvent_QueryAISuggestApplied( applicationStore.telemetryService, ); createQueryState.setQueryName(aiSuggestion.title); createQueryState.setQueryDescription(aiSuggestion.description); setAISuggestion(undefined); }; const discardAISuggestion = (): void => { LegendQueryTelemetryHelper.logEvent_QueryAISuggestDiscarded( applicationStore.telemetryService, ); setAISuggestion(undefined); }; const debouncedLoadQueries = useMemo( () => debounce((input: string): void => { flowResult( createQueryState.editorStore.searchExistingQueryName(input), ).catch(applicationStore.alertUnhandledError); }, 500), [applicationStore, createQueryState.editorStore], ); const setFocus = (): void => { nameInputRef.current?.focus(); }; const changeName: React.ChangeEventHandler = (event) => { createQueryState.setQueryName(event.target.value); }; useEffect(() => { setTimeout(() => setFocus(), 1); }, []); useEffect(() => { const searchText = createQueryState.queryName; debouncedLoadQueries.cancel(); debouncedLoadQueries(searchText); }, [ createQueryState.queryName, debouncedLoadQueries, createQueryState.editorStore.queryLoaderState.queries, ]); return (
Enter Query Name {aiSuggestion && ( AI Suggestion )}
{isExistingQueryName && (
)}
Enter Query Description