diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/apps/AbstractNativeApplication.java b/openmetadata-service/src/main/java/org/openmetadata/service/apps/AbstractNativeApplication.java index 6871a9cb6e7..3c574f3e49e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/apps/AbstractNativeApplication.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/apps/AbstractNativeApplication.java @@ -109,7 +109,7 @@ public class AbstractNativeApplication implements NativeApplication { // Get Pipeline IngestionPipeline dataInsightPipeline = getIngestionPipeline(createPipelineRequest, String.format("%sBot", app.getName()), "admin") - .withProvider(ProviderType.SYSTEM); + .withProvider(ProviderType.USER); ingestionPipelineRepository.setFullyQualifiedName(dataInsightPipeline); ingestionPipelineRepository.initializeEntity(dataInsightPipeline); @@ -121,7 +121,7 @@ public class AbstractNativeApplication implements NativeApplication { dataInsightPipeline.getId(), Entity.APPLICATION, Entity.INGESTION_PIPELINE, - Relationship.CONTAINS.ordinal()); + Relationship.HAS.ordinal()); } catch (Exception ex) { LOG.error("[IngestionPipelineResource] Failed in Creating Reindex and Insight Pipeline", ex); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/insights/DataInsightsReportApp.java b/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/insights/DataInsightsReportApp.java index 993327e16ea..d6a6e44f84a 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/insights/DataInsightsReportApp.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/insights/DataInsightsReportApp.java @@ -111,22 +111,27 @@ public class DataInsightsReportApp extends AbstractNativeApplication { emails.add(user.getEmail()); } } - DataInsightTotalAssetTemplate totalAssetTemplate = - createTotalAssetTemplate(searchClient, team.getName(), scheduleTime, currentTime, numberOfDaysChange); - DataInsightDescriptionAndOwnerTemplate descriptionTemplate = - createDescriptionTemplate(searchClient, team.getName(), scheduleTime, currentTime, numberOfDaysChange); - DataInsightDescriptionAndOwnerTemplate ownershipTemplate = - createOwnershipTemplate(searchClient, team.getName(), scheduleTime, currentTime, numberOfDaysChange); - DataInsightDescriptionAndOwnerTemplate tierTemplate = - createTierTemplate(searchClient, team.getName(), scheduleTime, currentTime, numberOfDaysChange); - EmailUtil.sendDataInsightEmailNotificationToUser( - emails, - totalAssetTemplate, - descriptionTemplate, - ownershipTemplate, - tierTemplate, - EmailUtil.getDataInsightReportSubject(), - EmailUtil.DATA_INSIGHT_REPORT_TEMPLATE); + + try { + DataInsightTotalAssetTemplate totalAssetTemplate = + createTotalAssetTemplate(searchClient, team.getName(), scheduleTime, currentTime, numberOfDaysChange); + DataInsightDescriptionAndOwnerTemplate descriptionTemplate = + createDescriptionTemplate(searchClient, team.getName(), scheduleTime, currentTime, numberOfDaysChange); + DataInsightDescriptionAndOwnerTemplate ownershipTemplate = + createOwnershipTemplate(searchClient, team.getName(), scheduleTime, currentTime, numberOfDaysChange); + DataInsightDescriptionAndOwnerTemplate tierTemplate = + createTierTemplate(searchClient, team.getName(), scheduleTime, currentTime, numberOfDaysChange); + EmailUtil.sendDataInsightEmailNotificationToUser( + emails, + totalAssetTemplate, + descriptionTemplate, + ownershipTemplate, + tierTemplate, + EmailUtil.getDataInsightReportSubject(), + EmailUtil.DATA_INSIGHT_REPORT_TEMPLATE); + } catch (Exception ex) { + LOG.error("[DataInsightReport] Failed for Team: {}, Reason : {}", team.getName(), ex.getMessage()); + } } } } @@ -135,23 +140,27 @@ public class DataInsightsReportApp extends AbstractNativeApplication { throws ParseException, IOException, TemplateException { // Get Admins Set emailList = getAdminsData(CreateEventSubscription.SubscriptionType.DATA_INSIGHT); - - DataInsightTotalAssetTemplate totalAssetTemplate = - createTotalAssetTemplate(searchClient, null, scheduleTime, currentTime, numberOfDaysChange); - DataInsightDescriptionAndOwnerTemplate descriptionTemplate = - createDescriptionTemplate(searchClient, null, scheduleTime, currentTime, numberOfDaysChange); - DataInsightDescriptionAndOwnerTemplate ownershipTemplate = - createOwnershipTemplate(searchClient, null, scheduleTime, currentTime, numberOfDaysChange); - DataInsightDescriptionAndOwnerTemplate tierTemplate = - createTierTemplate(searchClient, null, scheduleTime, currentTime, numberOfDaysChange); - EmailUtil.sendDataInsightEmailNotificationToUser( - emailList, - totalAssetTemplate, - descriptionTemplate, - ownershipTemplate, - tierTemplate, - EmailUtil.getDataInsightReportSubject(), - EmailUtil.DATA_INSIGHT_REPORT_TEMPLATE); + try { + // Build Insights Report + DataInsightTotalAssetTemplate totalAssetTemplate = + createTotalAssetTemplate(searchClient, null, scheduleTime, currentTime, numberOfDaysChange); + DataInsightDescriptionAndOwnerTemplate descriptionTemplate = + createDescriptionTemplate(searchClient, null, scheduleTime, currentTime, numberOfDaysChange); + DataInsightDescriptionAndOwnerTemplate ownershipTemplate = + createOwnershipTemplate(searchClient, null, scheduleTime, currentTime, numberOfDaysChange); + DataInsightDescriptionAndOwnerTemplate tierTemplate = + createTierTemplate(searchClient, null, scheduleTime, currentTime, numberOfDaysChange); + EmailUtil.sendDataInsightEmailNotificationToUser( + emailList, + totalAssetTemplate, + descriptionTemplate, + ownershipTemplate, + tierTemplate, + EmailUtil.getDataInsightReportSubject(), + EmailUtil.DATA_INSIGHT_REPORT_TEMPLATE); + } catch (Exception ex) { + LOG.error("[DataInsightReport] Failed for Admin, Reason : {}", ex.getMessage(), ex); + } } private List getAvailableKpi() { diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/AppRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/AppRepository.java index 77bd93a4375..4c0772b1b88 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/AppRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/AppRepository.java @@ -1,5 +1,6 @@ package org.openmetadata.service.jdbi3; +import static org.openmetadata.schema.type.Include.ALL; import static org.openmetadata.service.resources.teams.UserResource.getUser; import java.util.ArrayList; @@ -52,6 +53,17 @@ public class AppRepository extends EntityRepository { return entity.withBot(getBotUser(entity)); } + protected List getIngestionPipelines(App service) { + List pipelines = + findToRecords(service.getId(), entityType, Relationship.HAS, Entity.INGESTION_PIPELINE); + List ingestionPipelines = new ArrayList<>(); + for (CollectionDAO.EntityRelationshipRecord entityRelationshipRecord : pipelines) { + ingestionPipelines.add( + Entity.getEntityReferenceById(Entity.INGESTION_PIPELINE, entityRelationshipRecord.getId(), ALL)); + } + return ingestionPipelines; + } + public AppMarketPlaceRepository getMarketPlace() { return (AppMarketPlaceRepository) Entity.getEntityRepository(Entity.APP_MARKET_PLACE_DEF); } @@ -194,6 +206,15 @@ public class AppRepository extends EntityRepository { } } + @Override + protected void cleanup(App app) { + // Remove the Pipelines for Application + List pipelineRef = getIngestionPipelines(app); + pipelineRef.forEach( + (reference) -> Entity.deleteEntity("admin", reference.getType(), reference.getId(), true, true)); + super.cleanup(app); + } + public AppRunRecord getLatestAppRuns(UUID appId) { String json = daoCollection.appExtensionTimeSeriesDao().getLatestAppRun(appId); return JsonUtils.readValue(json, AppRunRecord.class); diff --git a/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/DataInsightsApplication.json b/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/DataInsightsApplication.json index 85d1a10113a..29b5e483aac 100644 --- a/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/DataInsightsApplication.json +++ b/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/DataInsightsApplication.json @@ -2,7 +2,7 @@ "name": "DataInsightsApplication", "fullyQualifiedName": "DataInsightsApplication", "displayName": "Data Insights", - "description": "Open-metadata Data Insights Application runs a Data Insights Pipeline on the Ingestion Framework.", + "description": "OpenMetadata Data Insights Application runs a Data Insights Pipeline on the Ingestion Framework.", "features": "Run Ingestion Framework Data Insight Module.", "appType": "external", "appScreenshots": ["DataInsightsPic1.png"], diff --git a/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/DataInsightsReportApplication.json b/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/DataInsightsReportApplication.json index dbb9e1e1229..f9a806825fc 100644 --- a/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/DataInsightsReportApplication.json +++ b/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/DataInsightsReportApplication.json @@ -2,7 +2,7 @@ "name": "DataInsightsReportApplication", "fullyQualifiedName": "DataInsightsReportApplication", "displayName": "Data Insights Report", - "description": "Open-metadata Data Insights Report Application sends a data insight report.", + "description": "OpenMetadata Data Insights Report Application sends a data insight report.", "features": "Data Insights Application. Send Email Reports.", "appType": "internal", "appScreenshots": ["DataInsightsReportPic1.png"], diff --git a/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/SearchIndexingApplication.json b/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/SearchIndexingApplication.json index 27df0f81c72..ae055175e03 100644 --- a/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/SearchIndexingApplication.json +++ b/openmetadata-service/src/main/resources/json/data/appMarketPlaceDefinition/SearchIndexingApplication.json @@ -2,8 +2,8 @@ "name": "SearchIndexingApplication", "fullyQualifiedName": "SearchIndexingApplication", "displayName": "Search Indexing", - "description": "Open-metadata connects with Elastic/Open Search to provide search feature for Data Assets. This application provides additional features related to ES/OS.", - "features": "Sync Open-metadata and Elastic Search and Recreate Indexes.", + "description": "OpenMetadata connects with Elastic/Open Search to provide search feature for Data Assets. This application provides additional features related to ES/OS.", + "features": "Sync OpenMetadata and Elastic Search and Recreate Indexes.", "appType": "internal", "appScreenshots": ["SearchIndexPic1.png"], "developer": "Collate Inc.", diff --git a/openmetadata-service/src/main/resources/json/data/policy/ApplicationBotPolicy.json b/openmetadata-service/src/main/resources/json/data/policy/ApplicationBotPolicy.json index 90b678823d5..af75390bcc4 100644 --- a/openmetadata-service/src/main/resources/json/data/policy/ApplicationBotPolicy.json +++ b/openmetadata-service/src/main/resources/json/data/policy/ApplicationBotPolicy.json @@ -11,7 +11,7 @@ "name": "ApplicationBotRule-Allow", "description" : "Allow ingestion bots to read data entities", "resources" : ["All"], - "operations": ["ViewAll"], + "operations": ["Create", "EditAll", "ViewAll", "Delete"], "effect": "allow" } ] diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/DataInsightsPic1.png b/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/DataInsightsPic1.png index d0b66609f5f..a62be650f9e 100644 Binary files a/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/DataInsightsPic1.png and b/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/DataInsightsPic1.png differ diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/DataInsightsReportPic1.png b/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/DataInsightsReportPic1.png index afcc4f479eb..90314de2547 100644 Binary files a/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/DataInsightsReportPic1.png and b/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/DataInsightsReportPic1.png differ diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/SearchIndexPic1.png b/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/SearchIndexPic1.png index 3f6b927fab3..11fed7d3a9d 100644 Binary files a/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/SearchIndexPic1.png and b/openmetadata-ui/src/main/resources/ui/src/assets/img/appScreenshots/SearchIndexPic1.png differ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/AddDataQualityTest.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/AddDataQualityTest.interface.ts index ef505039131..d79bead9a3b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/AddDataQualityTest.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/AddDataQualityTest.interface.ts @@ -46,6 +46,10 @@ export interface TestSuiteSchedulerProps { onSubmit: (repeatFrequency: string) => void; onCancel: () => void; isQuartzCron?: boolean; + buttonProps?: { + okText: string; + cancelText: string; + }; } export interface RightPanelProps { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/components/TestSuiteScheduler.tsx b/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/components/TestSuiteScheduler.tsx index 0e46d173b6f..8785b1bbfbb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/components/TestSuiteScheduler.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/AddDataQualityTest/components/TestSuiteScheduler.tsx @@ -19,6 +19,7 @@ import { TestSuiteSchedulerProps } from '../AddDataQualityTest.interface'; const TestSuiteScheduler: React.FC = ({ initialData, + buttonProps, onCancel, onSubmit, isQuartzCron = false, @@ -44,12 +45,14 @@ const TestSuiteScheduler: React.FC = ({ - + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.component.tsx index 350dc79260c..a738e3fdd76 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.component.tsx @@ -50,9 +50,11 @@ import { import { ServiceCategory } from '../../../enums/service.enum'; import { App, + AppType, ScheduleTimeline, } from '../../../generated/entity/applications/app'; import { + deployApp, getApplicationByName, patchApplication, triggerOnDemandApp, @@ -86,7 +88,7 @@ const AppDetails = () => { const fetchAppDetails = useCallback(async () => { setIsLoading(true); try { - const data = await getApplicationByName(fqn, 'owner'); + const data = await getApplicationByName(fqn, ['owner', 'pipelines']); setAppData(data); const schema = await import( `../../../utils/ApplicationSchemas/${fqn}.json` @@ -223,7 +225,25 @@ const AppDetails = () => { const onDemandTrigger = async () => { try { await triggerOnDemandApp(appData?.fullyQualifiedName ?? ''); - showSuccessToast(t('message.application-trigger-successfully')); + showSuccessToast( + t('message.application-action-successfully', { + action: t('label.triggered-lowercase'), + }) + ); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const onDeployTrigger = async () => { + try { + await deployApp(appData?.fullyQualifiedName ?? ''); + showSuccessToast( + t('message.application-action-successfully', { + action: t('label.deploy'), + }) + ); + fetchAppDetails(); } catch (error) { showErrorToast(error as AxiosError); } @@ -231,7 +251,9 @@ const AppDetails = () => { const tabs = useMemo(() => { const tabConfiguration = - appData && appData.appConfiguration && jsonSchema + appData?.appConfiguration && + appData.appType === AppType.Internal && + jsonSchema ? [ { label: ( @@ -276,6 +298,7 @@ const AppDetails = () => { appData={appData} onCancel={onBrowseAppsClick} onDemandTrigger={onDemandTrigger} + onDeployTrigger={onDeployTrigger} onSave={onAppScheduleSave} /> )} @@ -283,17 +306,24 @@ const AppDetails = () => { ), }, ...tabConfiguration, - { - label: ( - - ), - key: ApplicationTabs.HISTORY, - children: ( -
- -
- ), - }, + ...(appData?.appType === AppType.Internal + ? [ + { + label: ( + + ), + key: ApplicationTabs.HISTORY, + children: ( +
+ +
+ ), + }, + ] + : []), ]; }, [appData, jsonSchema]); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.interface.ts new file mode 100644 index 00000000000..14763c2089b --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppDetails/AppDetails.interface.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2023 Collate. + * 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 { PipelineStatus } from '../../../generated/entity/services/ingestionPipelines/ingestionPipeline'; + +export interface DataInsightLatestRun { + lastIngestionLogs: { + data_insight_task: string; + total: string; + }; + pipelineStatus: PipelineStatus; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppInstallVerifyCard/AppInstallVerifyCard.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppInstallVerifyCard/AppInstallVerifyCard.component.tsx index fed87e5b82c..c33af00a5c5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppInstallVerifyCard/AppInstallVerifyCard.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppInstallVerifyCard/AppInstallVerifyCard.component.tsx @@ -39,6 +39,7 @@ import { AppInstallVerifyCardProps } from './AppInstallVerifyCard.interface'; const AppInstallVerifyCard = ({ appData, + nextButtonLabel, onCancel, onSave, }: AppInstallVerifyCardProps) => { @@ -135,7 +136,7 @@ const AppInstallVerifyCard = ({ key="save-btn" type="primary" onClick={onSave}> - {t('label.configure')} + {nextButtonLabel} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppInstallVerifyCard/AppInstallVerifyCard.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppInstallVerifyCard/AppInstallVerifyCard.interface.ts index b6818061021..08f1d8b92b7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppInstallVerifyCard/AppInstallVerifyCard.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppInstallVerifyCard/AppInstallVerifyCard.interface.ts @@ -14,6 +14,7 @@ import { AppMarketPlaceDefinition } from '../../../generated/entity/applications export interface AppInstallVerifyCardProps { appData: AppMarketPlaceDefinition; + nextButtonLabel: string; onSave: () => void; onCancel: () => void; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.component.tsx index 99b6ede4b3c..6e34c847e35 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.component.tsx @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Button, Col, Row } from 'antd'; +import { Button, Col, Row, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { AxiosError } from 'axios'; import { capitalize, isNull } from 'lodash'; @@ -23,13 +23,20 @@ import React, { useState, } from 'react'; import { useTranslation } from 'react-i18next'; -import { useParams } from 'react-router-dom'; +import { useHistory, useParams } from 'react-router-dom'; +import { NO_DATA_PLACEHOLDER } from '../../../constants/constants'; +import { GlobalSettingOptions } from '../../../constants/GlobalSettings.constants'; +import { AppType } from '../../../generated/entity/applications/app'; import { Status } from '../../../generated/entity/applications/appRunRecord'; import { Paging } from '../../../generated/type/paging'; import { usePaging } from '../../../hooks/paging/usePaging'; -import { getApplicationRuns } from '../../../rest/applicationAPI'; +import { + getApplicationRuns, + getLatestApplicationRuns, +} from '../../../rest/applicationAPI'; import { getStatusTypeForApplication } from '../../../utils/ApplicationUtils'; import { formatDateTime } from '../../../utils/date-time/DateTimeUtils'; +import { getLogsViewerPath } from '../../../utils/RouterUtils'; import { showErrorToast } from '../../../utils/ToastUtils'; import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder'; import NextPrevious from '../../common/next-previous/NextPrevious'; @@ -44,7 +51,10 @@ import { } from './AppRunsHistory.interface'; const AppRunsHistory = forwardRef( - ({ maxRecords, showPagination = true }: AppRunsHistoryProps, ref) => { + ( + { appData, maxRecords, showPagination = true }: AppRunsHistoryProps, + ref + ) => { const { t } = useTranslation(); const { fqn } = useParams<{ fqn: string }>(); const [isLoading, setIsLoading] = useState(true); @@ -62,9 +72,25 @@ const AppRunsHistory = forwardRef( handlePageSizeChange, } = usePaging(); + const history = useHistory(); + + const isExternalApp = useMemo( + () => appData?.appType === AppType.External, + [appData] + ); + const handleRowExpandable = useCallback( (key?: string) => { if (key) { + if (isExternalApp && appData) { + return history.push( + getLogsViewerPath( + GlobalSettingOptions.APPLICATIONS, + appData.name ?? '', + appData.name ?? '' + ) + ); + } if (expandedRowKeys.includes(key)) { setExpandedRowKeys((prev) => prev.filter((item) => item !== key)); } else { @@ -95,6 +121,9 @@ const AppRunsHistory = forwardRef( title: t('label.run-type'), dataIndex: 'runType', key: 'runType', + render: (runType) => ( + {runType ?? NO_DATA_PLACEHOLDER} + ), }, { title: t('label.status'), @@ -143,18 +172,33 @@ const AppRunsHistory = forwardRef( async (pagingOffset?: Paging) => { try { setIsLoading(true); - const { data, paging } = await getApplicationRuns(fqn, { - offset: pagingOffset?.offset ?? 0, - limit: maxRecords ?? pageSize, - }); - setAppRunsHistoryData( - data.map((item) => ({ - ...item, - id: `${item.appId}-${item.runType}-${item.timestamp}`, - })) - ); - handlePagingChange(paging); + if (isExternalApp) { + const res = await getLatestApplicationRuns(fqn); + + setAppRunsHistoryData([ + { + ...res, + timestamp: res.pipelineStatus.timestamp, + status: (res.pipelineStatus.pipelineState ?? + Status.Failed) as Status, + id: `${res.pipelineStatus.runId}-${res.pipelineStatus.timestamp}`, + }, + ]); + } else { + const { data, paging } = await getApplicationRuns(fqn, { + offset: pagingOffset?.offset ?? 0, + limit: maxRecords ?? pageSize, + }); + + setAppRunsHistoryData( + data.map((item) => ({ + ...item, + id: `${item.appId}-${item.runType}-${item.timestamp}`, + })) + ); + handlePagingChange(paging); + } } catch (err) { showErrorToast(err as AxiosError); } finally { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.interface.ts index 24214c05b53..7898a8bb9c5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppRunsHistory/AppRunsHistory.interface.ts @@ -11,6 +11,7 @@ * limitations under the License. */ +import { App } from '../../../generated/entity/applications/app'; import { AppRunRecord } from '../../../generated/entity/applications/appRunRecord'; export interface AppRunRecordWithId extends AppRunRecord { @@ -24,4 +25,5 @@ export interface AppRunsHistoryRef { export interface AppRunsHistoryProps { maxRecords?: number; showPagination?: boolean; + appData?: App; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppSchedule/AppSchedule.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppSchedule/AppSchedule.component.tsx index 4aeca95a007..b8a4bca8edb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppSchedule/AppSchedule.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Applications/AppSchedule/AppSchedule.component.tsx @@ -12,12 +12,23 @@ */ import { Button, Col, Divider, Modal, Row, Space, Typography } from 'antd'; import cronstrue from 'cronstrue'; -import React, { useMemo, useRef, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; -import { AppScheduleClass } from '../../../generated/entity/applications/app'; +import { + AppScheduleClass, + AppType, +} from '../../../generated/entity/applications/app'; import { PipelineType } from '../../../generated/entity/services/ingestionPipelines/ingestionPipeline'; +import { getIngestionPipelineByFqn } from '../../../rest/ingestionPipelineAPI'; import { getIngestionFrequency } from '../../../utils/CommonUtils'; import TestSuiteScheduler from '../../AddDataQualityTest/components/TestSuiteScheduler'; +import Loader from '../../Loader/Loader'; import AppRunsHistory from '../AppRunsHistory/AppRunsHistory.component'; import { AppRunsHistoryRef } from '../AppRunsHistory/AppRunsHistory.interface'; import { AppScheduleProps } from './AppScheduleProps.interface'; @@ -26,10 +37,35 @@ const AppSchedule = ({ appData, onSave, onDemandTrigger, + onDeployTrigger, }: AppScheduleProps) => { const { t } = useTranslation(); const [showModal, setShowModal] = useState(false); const appRunsHistoryRef = useRef(null); + const [isPipelineDeployed, setIsPipelineDeployed] = useState(false); + const [isLoading, setIsLoading] = useState(true); + + const fetchPipelineDetails = useCallback(async () => { + setIsLoading(true); + try { + if ( + appData.appType === AppType.External && + appData.pipelines && + appData.pipelines.length > 0 + ) { + const fqn = appData.pipelines[0].fullyQualifiedName ?? ''; + const pipelineData = await getIngestionPipelineByFqn(fqn); + + setIsPipelineDeployed(pipelineData.deployed ?? false); + } else { + setIsPipelineDeployed(false); + } + } catch (error) { + setIsPipelineDeployed(false); + } finally { + setIsLoading(false); + } + }, [appData]); const cronString = useMemo(() => { if (appData.appSchedule) { @@ -58,6 +94,33 @@ const AppSchedule = ({ appRunsHistoryRef.current?.refreshAppHistory(); }; + const appRunHistory = useMemo(() => { + if (appData.appType === AppType.Internal || isPipelineDeployed) { + return ( + + ); + } + + return ( + + {t('message.no-ingestion-pipeline-found')} + + ); + }, [appData, isPipelineDeployed, appRunsHistoryRef]); + + useEffect(() => { + fetchPipelineDetails(); + }, []); + + if (isLoading) { + return ; + } + return ( <> @@ -90,6 +153,15 @@ const AppSchedule = ({ + {appData.appType === AppType.External && ( + + )} +