UNPKG

@finos/legend-application-marketplace

Version:
512 lines (464 loc) 17 kB
/** * 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 { type GeneratorFn, ActionState, assertErrorThrown, isNonNullable, LogEvent, UserSearchService, } from '@finos/legend-shared'; import { type ApplicationStore, LegendApplicationTelemetryHelper, APPLICATION_EVENT, DEFAULT_TAB_SIZE, } from '@finos/legend-application'; import { action, flow, makeObservable, observable } from 'mobx'; import { DepotServerClient, StoreProjectData, } from '@finos/legend-server-depot'; import { MarketplaceServerClient, RegistryServerClient, TerminalAccessServerClient, } from '@finos/legend-server-marketplace'; import { type V1_EngineServerClient, getCurrentUserIDFromEngineServer, V1_entitlementsDataProductDetailsResponseToDataProductDetails, V1_PureGraphManager, V1_RemoteEngine, } from '@finos/legend-graph'; import type { LegendMarketplaceApplicationConfig } from '../application/LegendMarketplaceApplicationConfig.js'; import type { LegendMarketplacePluginManager } from '../application/LegendMarketplacePluginManager.js'; import { LegendMarketplaceEventHelper } from '../__lib__/LegendMarketplaceEventHelper.js'; import { LakehouseContractServerClient, LakehouseIngestServerClient, LakehousePlatformServerClient, LakehouseWorkflowServerClient, PermitWorkflowServerClient, } from '@finos/legend-server-lakehouse'; import { CartStore } from './cart/CartStore.js'; import { PendingTasksCache } from './lakehouse/PendingTasksCache.js'; import { parseGAVCoordinates, type Entity } from '@finos/legend-storage'; import { V1_deserializeDataSpace } from '@finos/legend-extension-dsl-data-space/graph'; import { DevelopmentLegendMarketplaceEnvState, LegendMarketplaceEnv, ProdLegendMarketplaceEnvState, ProdParallelLegendMarketplaceEnvState, type LegendMarketplaceEnvState, } from './LegendMarketplaceEnvState.js'; import { ProductCardState } from './lakehouse/dataProducts/ProductCardState.js'; import { convertEntitlementsDataProductDetailsToSearchResult, convertLegacyDataProductToSearchResult, convertTrendingEntryToSearchResult, } from '../utils/SearchUtils.js'; import { LakehouseDataProductService } from './lakehouse/LakehouseDataProductService.js'; export type LegendMarketplaceApplicationStore = ApplicationStore< LegendMarketplaceApplicationConfig, LegendMarketplacePluginManager >; export class LegendMarketplaceBaseStore { readonly applicationStore: LegendMarketplaceApplicationStore; readonly envState: LegendMarketplaceEnvState; readonly adjacentEnvState: LegendMarketplaceEnvState | undefined; readonly marketplaceServerClient: MarketplaceServerClient; readonly depotServerClient: DepotServerClient; readonly lakehouseContractServerClient: LakehouseContractServerClient; readonly lakehousePlatformServerClient: LakehousePlatformServerClient; readonly lakehouseIngestServerClient: LakehouseIngestServerClient; readonly engineServerClient: V1_EngineServerClient; readonly registryServerClient: RegistryServerClient | undefined; readonly pluginManager: LegendMarketplacePluginManager; readonly remoteEngine: V1_RemoteEngine; readonly userSearchService: UserSearchService | undefined; readonly lakehouseWorkflowServerClient: LakehouseWorkflowServerClient; readonly permitWorkflowServerClient: PermitWorkflowServerClient | undefined; readonly lakehouseDataProductService: LakehouseDataProductService; readonly cartStore: CartStore; readonly terminalAccessServerClient: TerminalAccessServerClient; readonly pendingTasksCache: PendingTasksCache; readonly initState = ActionState.create(); showDemoModal = false; constructor(applicationStore: LegendMarketplaceApplicationStore) { makeObservable<LegendMarketplaceBaseStore>(this, { showDemoModal: observable, setDemoModal: action, initialize: flow, }); this.applicationStore = applicationStore; this.pluginManager = applicationStore.pluginManager; // marketplace this.envState = applicationStore.config.dataProductEnv === LegendMarketplaceEnv.PRODUCTION ? new ProdLegendMarketplaceEnvState() : applicationStore.config.dataProductEnv === LegendMarketplaceEnv.PRODUCTION_PARALLEL ? new ProdParallelLegendMarketplaceEnvState() : new DevelopmentLegendMarketplaceEnvState(); this.adjacentEnvState = this.buildAdjacentEnvState(); this.marketplaceServerClient = new MarketplaceServerClient({ serverUrl: this.applicationStore.config.marketplaceServerUrl, subscriptionUrl: this.applicationStore.config.marketplaceSubscriptionUrl, }); this.marketplaceServerClient.setTracerService( this.applicationStore.tracerService, ); // registry if (this.applicationStore.config.registryUrl) { this.registryServerClient = new RegistryServerClient({ baseUrl: this.applicationStore.config.registryUrl, }); this.registryServerClient.setTracerService( this.applicationStore.tracerService, ); } // depot this.depotServerClient = new DepotServerClient({ serverUrl: this.applicationStore.config.depotServerUrl, }); this.depotServerClient.setTracerService( this.applicationStore.tracerService, ); // lakehouse contract this.lakehouseContractServerClient = new LakehouseContractServerClient({ baseUrl: this.applicationStore.config.lakehouseServerUrl, }); this.lakehouseContractServerClient.setTracerService( this.applicationStore.tracerService, ); // terminal this.terminalAccessServerClient = new TerminalAccessServerClient({ baseUrl: this.applicationStore.config.terminalServerUrl, }); this.terminalAccessServerClient.setTracerService( this.applicationStore.tracerService, ); // lakehouse platform this.lakehousePlatformServerClient = new LakehousePlatformServerClient( this.applicationStore.config.lakehousePlatformUrl, ); this.lakehousePlatformServerClient.setTracerService( this.applicationStore.tracerService, ); // lakehouse workflow this.lakehouseWorkflowServerClient = new LakehouseWorkflowServerClient({ baseUrl: this.applicationStore.config.lakehouseWorkflowServerUrl, }); this.lakehouseWorkflowServerClient.setTracerService( this.applicationStore.tracerService, ); // permit + eTask workflow if (this.applicationStore.config.lakehousePermitWorkflowServerUrl) { this.permitWorkflowServerClient = new PermitWorkflowServerClient({ authBaseUrl: this.applicationStore.config.lakehouseServerUrl, workflowBaseUrl: this.applicationStore.config.lakehousePermitWorkflowServerUrl, }); this.permitWorkflowServerClient.setTracerService( this.applicationStore.tracerService, ); } else { this.permitWorkflowServerClient = undefined; } // lakehouse ingest this.lakehouseIngestServerClient = new LakehouseIngestServerClient( undefined, ); this.lakehouseIngestServerClient.setTracerService( this.applicationStore.tracerService, ); this.remoteEngine = new V1_RemoteEngine( { baseUrl: this.applicationStore.config.engineServerUrl, }, applicationStore.logService, ); this.engineServerClient = this.remoteEngine.getEngineServerClient(); this.engineServerClient.setTracerService(applicationStore.tracerService); // User search if (this.pluginManager.getUserPlugins().length > 0) { this.pluginManager .getUserPlugins() .forEach((plugin) => plugin.setup(this.applicationStore.config.marketplaceUserSearchUrl), ); this.userSearchService = new UserSearchService({ userProfileImageUrl: this.applicationStore.config.marketplaceUserProfileImageUrl, applicationDirectoryUrl: this.applicationStore.config.lakehouseEntitlementsConfig ?.applicationDirectoryUrl, }); this.userSearchService.registerPlugins( this.pluginManager.getUserPlugins(), ); } // Data Product service this.lakehouseDataProductService = new LakehouseDataProductService( this, this.lakehousePlatformServerClient, this.lakehouseContractServerClient, ); // Initialize cart store this.cartStore = new CartStore(this); // Shared cache + in-flight dedupe for /datacontracts/tasks/pending this.pendingTasksCache = new PendingTasksCache( this.lakehouseContractServerClient, ); } buildAdjacentEnvState(): LegendMarketplaceEnvState | undefined { const adjacentEnv = this.envState.adjacentEnv; if (adjacentEnv) { return adjacentEnv === LegendMarketplaceEnv.PRODUCTION ? new ProdLegendMarketplaceEnvState() : new ProdParallelLegendMarketplaceEnvState(); } return undefined; } private buildVendorImageMap(): Map<string, string> { const vendorImageMap = new Map<string, string>(); const assetsBaseUrl = this.applicationStore.config.assetsBaseUrl; for (const [vendorName, filename] of Object.entries( this.applicationStore.config.assetsProductImageMap, )) { vendorImageMap.set(vendorName, `${assetsBaseUrl}/${filename}`); } return vendorImageMap; } async createInitializedGraphManager(): Promise<V1_PureGraphManager> { const graphManager = new V1_PureGraphManager( this.applicationStore.pluginManager, this.applicationStore.logService, this.remoteEngine, ); await graphManager.initialize( { env: this.applicationStore.config.env, tabSize: DEFAULT_TAB_SIZE, clientConfig: { baseUrl: this.applicationStore.config.engineServerUrl, }, }, { engine: this.remoteEngine }, ); return graphManager; } private parseDataProductEntries( entriesString: string, ): ( | { dataProductId: string; deploymentId: number; gav?: undefined } | { dataProductId: string; gav: string; deploymentId?: undefined } )[] { return entriesString .split(',') .map((entry) => { const vals = entry.split('/'); if (vals[0] === undefined || vals[1] === undefined) { return undefined; } const id = vals[0]; const secondPart = vals[1]; if (Number.isInteger(Number(secondPart))) { return { dataProductId: id, deploymentId: parseInt(secondPart), }; } else { return { dataProductId: id, gav: secondPart }; } }) .filter(isNonNullable); } async initHighlightedDataProducts( token: string | undefined, ): Promise<Record<string, ProductCardState[]> | undefined> { const highlightedConfig = this.applicationStore.config.options.highlightedDataProducts; if (!highlightedConfig) { return undefined; } const sectionEntries = Object.entries(highlightedConfig); if (sectionEntries.length === 0) { return undefined; } const vendorImageMap = this.buildVendorImageMap(); const getDataProductState = async ( dataProductId: string, deploymentId: number, graphManager: V1_PureGraphManager, ) => { const rawResponse = await this.lakehouseContractServerClient.getDataProductByIdAndDID( dataProductId, deploymentId, token, ); const dataProductDetail = V1_entitlementsDataProductDetailsResponseToDataProductDetails( rawResponse, )[0]; if (dataProductDetail) { const searchResult = convertEntitlementsDataProductDetailsToSearchResult( dataProductDetail, ); return new ProductCardState( this, searchResult, graphManager, vendorImageMap, ); } else { return undefined; } }; const getLegacyDataProductState = async ( dataProductId: string, gav: string, graphManager: V1_PureGraphManager, ) => { const coordinates = parseGAVCoordinates(gav); const storeProject = new StoreProjectData(); storeProject.groupId = coordinates.groupId; storeProject.artifactId = coordinates.artifactId; const legacyDataProuct = await this.depotServerClient.getEntity( storeProject, coordinates.versionId, dataProductId, ); const dataSpace = V1_deserializeDataSpace( (legacyDataProuct as unknown as Entity).content, ); const searchResult = convertLegacyDataProductToSearchResult( dataSpace, coordinates.groupId, coordinates.artifactId, coordinates.versionId, ); return new ProductCardState( this, searchResult, graphManager, vendorImageMap, ); }; const graphManager = await this.createInitializedGraphManager(); const result: Record<string, ProductCardState[]> = {}; await Promise.all( sectionEntries.map(async ([sectionName, entriesString]) => { const entries = this.parseDataProductEntries(entriesString); const states = ( await Promise.all( entries.map(async (dataProduct) => dataProduct.deploymentId !== undefined ? getDataProductState( dataProduct.dataProductId, dataProduct.deploymentId, graphManager, ) : getLegacyDataProductState( dataProduct.dataProductId, dataProduct.gav, graphManager, ), ), ) ).filter(isNonNullable); states.forEach((state) => state.init(token)); result[sectionName] = states; }), ); return result; } async fetchTrendingDataProducts( token: string | undefined, ): Promise<Record<string, ProductCardState[]> | undefined> { const vendorImageMap = this.buildVendorImageMap(); const graphManager = await this.createInitializedGraphManager(); const trendingEntries = await this.marketplaceServerClient.getTrendingDataProducts( this.envState.lakehouseEnvironment, ); const states = trendingEntries.slice(0, 4).map((entry) => { const searchResult = convertTrendingEntryToSearchResult(entry); return new ProductCardState( this, searchResult, graphManager, vendorImageMap, ); }); states.forEach((state) => state.init(token)); return { Trending: states }; } setDemoModal(val: boolean): void { this.showDemoModal = val; } *initialize(): GeneratorFn<void> { if (!this.initState.isInInitialState) { this.applicationStore.notificationService.notifyIllegalState( 'Base store is re-initialized', ); return; } this.initState.inProgress(); // retrieved the user identity is not already configured if (this.applicationStore.identityService.isAnonymous) { try { this.applicationStore.identityService.setCurrentUser( (yield getCurrentUserIDFromEngineServer( this.applicationStore.config.engineServerUrl, )) as string, ); } catch (error) { assertErrorThrown(error); this.applicationStore.logService.error( LogEvent.create(APPLICATION_EVENT.IDENTITY_AUTO_FETCH__FAILURE), error, ); this.applicationStore.notificationService.notifyWarning(error.message); } } // setup telemetry service this.applicationStore.telemetryService.setup(); LegendApplicationTelemetryHelper.logEvent_ApplicationInitializationSucceeded( this.applicationStore.telemetryService, this.applicationStore, ); LegendMarketplaceEventHelper.notify_ApplicationLoadSucceeded( this.applicationStore.eventService, ); // Initialize cart store to load existing items try { yield* this.cartStore.initialize(); } catch (error) { assertErrorThrown(error); this.applicationStore.logService.warn( LogEvent.create(APPLICATION_EVENT.IDENTITY_AUTO_FETCH__FAILURE), 'Failed to initialize cart store', error, ); // Don't show notification as cart initialization failure shouldn't block app startup } this.initState.complete(); } }