From 09db201a57cf187b5effb653f2375def275bdcf6 Mon Sep 17 00:00:00 2001 From: Kenil Shah <32725411+Kenil27@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:55:47 +0530 Subject: [PATCH] UI: Add option to stop the running application (#18759) * Add Stop functionality for Applications * show kill button by default * Add option to stop the running application * fix: show stop button for ruuning & activeerror status * add localisation files * added unit tests * update localisation files --------- Co-authored-by: Sriharsha Chintalapani --- .../apps/AbstractNativeApplication.java | 8 +- .../bundles/searchIndex/SearchIndexApp.java | 16 +- .../service/apps/scheduler/AppScheduler.java | 54 +++++-- .../service/jdbi3/AppRepository.java | 8 - .../service/resources/apps/AppResource.java | 8 +- .../json/schema/system/eventPublisherJob.json | 3 +- .../StopScheduleRunModal.interface.ts | 20 +++ .../StopScheduleRunModal.test.tsx | 87 +++++++++++ .../StopScheduleRun/StopScheduleRunModal.tsx | 76 +++++++++ .../AppRunsHistory.component.tsx | 147 ++++++++++-------- .../AppRunsHistory/AppRunsHistory.test.tsx | 42 +++++ .../ui/src/locale/languages/de-de.json | 2 + .../ui/src/locale/languages/en-us.json | 2 + .../ui/src/locale/languages/es-es.json | 2 + .../ui/src/locale/languages/fr-fr.json | 2 + .../ui/src/locale/languages/gl-es.json | 2 + .../ui/src/locale/languages/he-he.json | 2 + .../ui/src/locale/languages/ja-jp.json | 2 + .../ui/src/locale/languages/nl-nl.json | 2 + .../ui/src/locale/languages/pr-pr.json | 2 + .../ui/src/locale/languages/pt-br.json | 2 + .../ui/src/locale/languages/pt-pt.json | 2 + .../ui/src/locale/languages/ru-ru.json | 2 + .../ui/src/locale/languages/th-th.json | 2 + .../ui/src/locale/languages/zh-cn.json | 2 + .../resources/ui/src/rest/applicationAPI.ts | 4 + 26 files changed, 405 insertions(+), 96 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.interface.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.tsx 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 6dd73db36b5..9bdc74f2183 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 @@ -48,7 +48,6 @@ public class AbstractNativeApplication implements NativeApplication { protected CollectionDAO collectionDAO; private App app; protected SearchRepository searchRepository; - protected boolean isJobInterrupted = false; // Default service that contains external apps' Ingestion Pipelines private static final String SERVICE_NAME = "OpenMetadata"; @@ -299,6 +298,11 @@ public class AbstractNativeApplication implements NativeApplication { @Override public void interrupt() throws UnableToInterruptJobException { LOG.info("Interrupting the job for app: {}", this.app.getName()); - isJobInterrupted = true; + stop(); + } + + protected void stop() { + LOG.info("Default stop behavior for app: {}", this.app.getName()); + // Default implementation: no-op or generic cleanup logic } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/SearchIndexApp.java b/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/SearchIndexApp.java index 2a356bf1024..fd34c2027f5 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/SearchIndexApp.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/SearchIndexApp.java @@ -161,12 +161,13 @@ public class SearchIndexApp extends AbstractNativeApplication { @Getter private EventPublisherJob jobData; private final Object jobDataLock = new Object(); - private volatile boolean stopped = false; private ExecutorService producerExecutor; private final ExecutorService jobExecutor = Executors.newCachedThreadPool(); private BlockingQueue producerQueue = new LinkedBlockingQueue<>(100); private final AtomicReference searchIndexStats = new AtomicReference<>(); private final AtomicReference batchSize = new AtomicReference<>(5); + private JobExecutionContext jobExecutionContext; + private volatile boolean stopped = false; public SearchIndexApp(CollectionDAO collectionDAO, SearchRepository searchRepository) { super(collectionDAO, searchRepository); @@ -190,6 +191,7 @@ public class SearchIndexApp extends AbstractNativeApplication { @Override public void startApp(JobExecutionContext jobExecutionContext) { try { + this.jobExecutionContext = jobExecutionContext; initializeJob(jobExecutionContext); String runType = (String) jobExecutionContext.getJobDetail().getJobDataMap().get("triggerType"); @@ -533,11 +535,17 @@ public class SearchIndexApp extends AbstractNativeApplication { } @SuppressWarnings("unused") - public void stopJob() { + @Override + public void stop() { LOG.info("Stopping reindexing job."); stopped = true; + jobData.setStatus(EventPublisherJob.Status.STOP_IN_PROGRESS); + sendUpdates(jobExecutionContext); shutdownExecutor(jobExecutor, "JobExecutor", 60, TimeUnit.SECONDS); shutdownExecutor(producerExecutor, "ProducerExecutor", 60, TimeUnit.SECONDS); + LOG.info("Stopped reindexing job."); + jobData.setStatus(EventPublisherJob.Status.STOPPED); + sendUpdates(jobExecutionContext); } private void processTask(IndexingTask task, JobExecutionContext jobExecutionContext) { @@ -596,7 +604,9 @@ public class SearchIndexApp extends AbstractNativeApplication { } LOG.error("Unexpected error during processing task for entity {}", entityType, e); } finally { - sendUpdates(jobExecutionContext); + if (!stopped) { + sendUpdates(jobExecutionContext); + } } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/apps/scheduler/AppScheduler.java b/openmetadata-service/src/main/java/org/openmetadata/service/apps/scheduler/AppScheduler.java index 38bd2c2c870..3163fc9a2e6 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/apps/scheduler/AppScheduler.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/apps/scheduler/AppScheduler.java @@ -263,30 +263,52 @@ public class AppScheduler { } public void stopApplicationRun(App application) { - if (application.getFullyQualifiedName() == null) { - throw new IllegalArgumentException("Application's fullyQualifiedName is null."); - } try { - // Interrupt any scheduled job JobDetail jobDetailScheduled = scheduler.getJobDetail(new JobKey(application.getName(), APPS_JOB_GROUP)); - if (jobDetailScheduled != null) { - LOG.debug("Stopping Scheduled Execution for App : {}", application.getName()); - scheduler.interrupt(jobDetailScheduled.getKey()); - } - - // Interrupt any on-demand job JobDetail jobDetailOnDemand = scheduler.getJobDetail( new JobKey( String.format("%s-%s", application.getName(), ON_DEMAND_JOB), APPS_JOB_GROUP)); - - if (jobDetailOnDemand != null) { - LOG.debug("Stopping On Demand Execution for App : {}", application.getName()); - scheduler.interrupt(jobDetailOnDemand.getKey()); + boolean isJobRunning = false; + // Check if the job is already running + List currentJobs = scheduler.getCurrentlyExecutingJobs(); + for (JobExecutionContext context : currentJobs) { + if ((jobDetailScheduled != null + && context.getJobDetail().getKey().equals(jobDetailScheduled.getKey())) + || (jobDetailOnDemand != null + && context.getJobDetail().getKey().equals(jobDetailOnDemand.getKey()))) { + isJobRunning = true; + } } - } catch (Exception ex) { - LOG.error("Failed to stop job execution.", ex); + if (!isJobRunning) { + throw new UnhandledServerException("There is no job running for the application."); + } + JobKey scheduledJobKey = new JobKey(application.getName(), APPS_JOB_GROUP); + if (jobDetailScheduled != null) { + LOG.debug("Stopping Scheduled Execution for App: {}", application.getName()); + scheduler.interrupt(scheduledJobKey); + try { + scheduler.deleteJob(scheduledJobKey); + } catch (SchedulerException ex) { + LOG.error("Failed to delete scheduled job: {}", scheduledJobKey, ex); + } + } else { + JobKey onDemandJobKey = + new JobKey( + String.format("%s-%s", application.getName(), ON_DEMAND_JOB), APPS_JOB_GROUP); + if (jobDetailOnDemand != null) { + LOG.debug("Stopping On Demand Execution for App: {}", application.getName()); + scheduler.interrupt(onDemandJobKey); + try { + scheduler.deleteJob(onDemandJobKey); + } catch (SchedulerException ex) { + LOG.error("Failed to delete on-demand job: {}", onDemandJobKey, ex); + } + } + } + } catch (SchedulerException ex) { + LOG.error("Failed to stop job execution for app: {}", application.getName(), ex); } } } 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 e4ecd6bb2c9..1c7cab44492 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 @@ -141,11 +141,7 @@ public class AppRepository extends EntityRepository { public void storeEntity(App entity, boolean update) { List ownerRefs = entity.getOwners(); entity.withOwners(null); - - // Store store(entity, update); - - // Restore entity fields entity.withOwners(ownerRefs); } @@ -178,7 +174,6 @@ public class AppRepository extends EntityRepository { } public final List listAll() { - // forward scrolling, if after == null then first page is being asked List jsons = dao.listAfterWithOffset(Integer.MAX_VALUE, 0); List entities = new ArrayList<>(); for (String json : jsons) { @@ -214,7 +209,6 @@ public class AppRepository extends EntityRepository { .listAppExtensionCountByName(app.getName(), extensionType.toString()); List entities = new ArrayList<>(); if (limitParam > 0) { - // forward scrolling, if after == null then first page is being asked List jsons = daoCollection .appExtensionTimeSeriesDao() @@ -274,7 +268,6 @@ public class AppRepository extends EntityRepository { app.getName(), startTime, extensionType.toString()); List entities = new ArrayList<>(); if (limitParam > 0) { - // forward scrolling, if after == null then first page is being asked List jsons = daoCollection .appExtensionTimeSeriesDao() @@ -287,7 +280,6 @@ public class AppRepository extends EntityRepository { return new ResultList<>(entities, offset, total); } else { - // limit == 0 , return total count of entity. return new ResultList<>(entities, null, total); } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java index f9066b215e0..53259cf8e32 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/AppResource.java @@ -269,13 +269,13 @@ public class AppResource extends EntityResource { @DefaultValue("10") @QueryParam("limit") @Min(0) - @Max(1000000) + @Max(1000) int limitParam, @Parameter(description = "Offset records. (0 to 1000000, default = 0)") @DefaultValue("0") @QueryParam("offset") @Min(0) - @Max(1000000) + @Max(1000) int offset, @Parameter( description = "Filter pipeline status after the given start timestamp", @@ -1013,9 +1013,9 @@ public class AppResource extends EntityResource { App app = repository.getByName(uriInfo, name, fields); if (Boolean.TRUE.equals(app.getSupportsInterrupt())) { if (app.getAppType().equals(AppType.Internal)) { - AppScheduler.getInstance().stopApplicationRun(app); + new Thread(() -> AppScheduler.getInstance().stopApplicationRun(app)).start(); return Response.status(Response.Status.OK) - .entity("Application will be stopped in some time.") + .entity("Application stop in progress. Please check status via.") .build(); } else { if (!app.getPipelines().isEmpty()) { diff --git a/openmetadata-spec/src/main/resources/json/schema/system/eventPublisherJob.json b/openmetadata-spec/src/main/resources/json/schema/system/eventPublisherJob.json index 3305971858d..e79867682b9 100644 --- a/openmetadata-spec/src/main/resources/json/schema/system/eventPublisherJob.json +++ b/openmetadata-spec/src/main/resources/json/schema/system/eventPublisherJob.json @@ -75,7 +75,8 @@ "active", "activeError", "stopped", - "success" + "success", + "stopInProgress" ] }, "failure": { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.interface.ts new file mode 100644 index 00000000000..fc7a0caf7b3 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.interface.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2022 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. + */ + +export interface StopScheduleRunModalProps { + appName: string; + isModalOpen: boolean; + displayName: string; + onClose: () => void; + onStopWorkflowsUpdate?: () => void; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.test.tsx new file mode 100644 index 00000000000..067aabdb394 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.test.tsx @@ -0,0 +1,87 @@ +/* + * Copyright 2024 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 { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; +import { stopApp } from '../../../rest/applicationAPI'; +import { showErrorToast } from '../../../utils/ToastUtils'; +import StopScheduleModal from './StopScheduleRunModal'; + +jest.mock('../../../rest/applicationAPI', () => ({ + stopApp: jest.fn(), +})); + +jest.mock('../../../utils/ToastUtils', () => ({ + showErrorToast: jest.fn(), + showSuccessToast: jest.fn(), +})); + +describe('StopScheduleModal', () => { + const mockProps = { + appName: 'test-app', + displayName: 'Test App', + isModalOpen: true, + onClose: jest.fn(), + onStopWorkflowsUpdate: jest.fn(), + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render the modal', () => { + render(); + + expect(screen.getByTestId('stop-modal')).toBeInTheDocument(); + }); + + it('should call stop app and display success toast on confirm', async () => { + (stopApp as jest.Mock).mockResolvedValueOnce({ status: 200 }); + + render(); + + const confirmButton = screen.getByText('label.confirm'); + fireEvent.click(confirmButton); + + expect(stopApp).toHaveBeenCalledWith('test-app'); + + await waitFor(() => { + expect(mockProps.onStopWorkflowsUpdate).toHaveBeenCalled(); + expect(mockProps.onClose).toHaveBeenCalled(); + }); + }); + + it('should call stop app and display error toast on failure', async () => { + (stopApp as jest.Mock).mockRejectedValueOnce(new Error('API Error')); + + render(); + + const confirmButton = screen.getByText('label.confirm'); + fireEvent.click(confirmButton); + + expect(stopApp).toHaveBeenCalledWith('test-app'); + + await waitFor(() => { + expect(showErrorToast).toHaveBeenCalledWith(new Error('API Error')); + expect(mockProps.onClose).toHaveBeenCalled(); + }); + }); + + it('should call onClose when cancel button is clicked', () => { + render(); + + const cancelButton = screen.getByText('label.cancel'); + fireEvent.click(cancelButton); + + expect(mockProps.onClose).toHaveBeenCalled(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.tsx new file mode 100644 index 00000000000..5372fd4706a --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/StopScheduleRun/StopScheduleRunModal.tsx @@ -0,0 +1,76 @@ +/* + * Copyright 2022 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 { Modal, Typography } from 'antd'; +import { AxiosError } from 'axios'; +import React, { FC, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { stopApp } from '../../../rest/applicationAPI'; +import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; +import { StopScheduleRunModalProps } from './StopScheduleRunModal.interface'; + +const StopScheduleModal: FC = ({ + appName, + isModalOpen, + displayName, + onClose, + onStopWorkflowsUpdate, +}) => { + const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(false); + + const handleConfirm = async () => { + setIsLoading(true); + try { + const { status } = await stopApp(appName); + if (status === 200) { + showSuccessToast( + t('message.application-stop', { + pipelineName: displayName, + }) + ); + onStopWorkflowsUpdate?.(); + } + } catch (error) { + // catch block error is unknown type so we have to cast it to respective type + showErrorToast(error as AxiosError); + } finally { + onClose(); + setIsLoading(false); + } + }; + + return ( + + + {t('message.are-you-sure-action-property', { + action: 'Stop', + propertyName: displayName, + })} + + + ); +}; + +export default StopScheduleModal; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.component.tsx index 8c9735a4071..7a9b049d3a2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.component.tsx @@ -59,6 +59,7 @@ import { PagingHandlerParams } from '../../../common/NextPrevious/NextPrevious.i import StatusBadge from '../../../common/StatusBadge/StatusBadge.component'; import { StatusType } from '../../../common/StatusBadge/StatusBadge.interface'; import Table from '../../../common/Table/Table'; +import StopScheduleModal from '../../../Modals/StopScheduleRun/StopScheduleRunModal'; import AppLogsViewer from '../AppLogsViewer/AppLogsViewer.component'; import { AppRunRecordWithId, @@ -78,6 +79,7 @@ const AppRunsHistory = forwardRef( AppRunRecordWithId[] >([]); const [expandedRowKeys, setExpandedRowKeys] = useState([]); + const [isStopModalOpen, setIsStopModalOpen] = useState(false); const { currentPage, @@ -132,29 +134,35 @@ const AppRunsHistory = forwardRef( const getActionButton = useCallback( (record: AppRunRecordWithId, index: number) => { - if (appData?.appType === AppType.Internal) { + if ( + appData?.appType === AppType.Internal || + (isExternalApp && index === 0) + ) { return ( - - ); - } else if (isExternalApp && index === 0) { - return ( - + <> + + {/* For status running or activewitherror and supportsInterrupt is true, show stop button */} + {(record.status === Status.Running || + record.status === Status.ActiveError) && + Boolean(appData?.supportsInterrupt) && ( + + )} + ); } else { return NO_DATA_PLACEHOLDER; @@ -347,47 +355,62 @@ const AppRunsHistory = forwardRef( }, [socket]); return ( - - - ( - - ), - showExpandColumn: false, - rowExpandable: (record) => !showLogAction(record), - expandedRowKeys, - }} - loading={isLoading} - locale={{ - emptyText: , - }} - pagination={false} - rowKey="id" - size="small" - /> - - - {showPagination && paginationVisible && ( - + + +
( + + ), + showExpandColumn: false, + rowExpandable: (record) => !showLogAction(record), + expandedRowKeys, + }} + loading={isLoading} + locale={{ + emptyText: , + }} + pagination={false} + rowKey="id" + size="small" /> - )} - - + + + {showPagination && paginationVisible && ( + + )} + + + {isStopModalOpen && ( + { + setIsStopModalOpen(false); + }} + onStopWorkflowsUpdate={() => { + fetchAppHistory(); + }} + /> + )} + ); } ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.test.tsx index 9b4223c9ed1..b8f49f1a728 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppRunsHistory/AppRunsHistory.test.tsx @@ -142,6 +142,15 @@ const mockProps2 = { }, }; +const mockProps3 = { + ...mockProps1, + appData: { + ...mockProps1.appData, + supportsInterrupt: true, + status: Status.Running, + }, +}; + describe('AppRunsHistory component', () => { it('should contain all necessary elements based on mockProps1', async () => { render(); @@ -160,6 +169,11 @@ describe('AppRunsHistory component', () => { expect(screen.queryByText('--')).not.toBeInTheDocument(); expect(screen.getByText('NextPrevious')).toBeInTheDocument(); + + // Verify Stop button is not present as initial status is success + const stopButton = screen.queryByTestId('stop-button'); + + expect(stopButton).not.toBeInTheDocument(); }); it('should show the error toast if fail in fetching app history', async () => { @@ -247,4 +261,32 @@ describe('AppRunsHistory component', () => { expect(screen.getByText('--')).toBeInTheDocument(); }); + + it('should render the stop button when conditions are met', async () => { + const mockRunRecordWithStopButton = { + ...mockApplicationData, + status: Status.Running, // Ensures Stop button condition is met + supportsInterrupt: true, + }; + mockGetApplicationRuns.mockReturnValueOnce({ + data: [mockRunRecordWithStopButton], + paging: { + offset: 0, + total: 1, + }, + }); + + render(); + await waitForElementToBeRemoved(() => screen.getByText('TableLoader')); + + const stopButton = screen.getByTestId('stop-button'); + + expect(stopButton).toBeInTheDocument(); + + act(() => { + userEvent.click(stopButton); + }); + + expect(screen.getByTestId('stop-modal')).toBeInTheDocument(); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json index 3caf8e881da..0947405042b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json @@ -1172,6 +1172,7 @@ "status": "Status", "stay-up-to-date": "Bleiben Sie auf dem neuesten Stand", "step": "Step", + "stop": "Stopp", "stop-re-index-all": "Stoppen Sie die erneute Indexierung aller", "stopped": "Gestoppt", "storage": "Speicher", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Customize OpenMetadata with your company logo, monogram, favicon and brand color.", "application-action-successfully": "Application {{action}} successfully", "application-disabled-message": "Application is currently disabled. Click on the ellipsis menu in header to enable it.", + "application-stop": "Anwendung wird gestoppt", "application-to-improve-data": "Improve your data using Applications for MetaPilot, Data Insights, and Search Indexing", "are-you-sure": "Sind Sie sicher?", "are-you-sure-action-property": "Are you sure you want to {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 06af07357ed..4fb3b3902bd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -1172,6 +1172,7 @@ "status": "Status", "stay-up-to-date": "Stay Up-to-date", "step": "Step", + "stop": "Stop", "stop-re-index-all": "Stop Re-Index", "stopped": "Stopped", "storage": "Storage", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Customize OpenMetadata with your company logo, monogram, favicon and brand color.", "application-action-successfully": "Application {{action}} successfully", "application-disabled-message": "Application is currently disabled. Click on the ellipsis menu in header to enable it.", + "application-stop": "Application stop is in progresss", "application-to-improve-data": "Improve your data using Applications for MetaPilot, Data Insights, and Search Indexing", "are-you-sure": "Are you sure?", "are-you-sure-action-property": "Are you sure you want to {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index f0cd7403390..bb70c637c9b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -1172,6 +1172,7 @@ "status": "Estado", "stay-up-to-date": "Manténgase Actualizado", "step": "Paso", + "stop": "Detener", "stop-re-index-all": "Parar el reindexado", "stopped": "Parado", "storage": "Almacenamiento", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Customize OpenMetadata with your company logo, monogram, favicon and brand color.", "application-action-successfully": "Aplicación {{action}} exitosamente", "application-disabled-message": "La aplicación está actualmente deshabilitada. Haz clic en el menú de puntos suspensivos en la cabecera para habilitarla.", + "application-stop": "La detención de la aplicación está en progreso", "application-to-improve-data": "Mejora tus datos utilizando Aplicaciones para MetaPilot, Data Insights y Indexación de Búsqueda", "are-you-sure": "¿Estás seguro?", "are-you-sure-action-property": "¿Estás seguro de que quieres {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index 10a561e223c..128903d0799 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -1172,6 +1172,7 @@ "status": "Statut", "stay-up-to-date": "Rester à Jour", "step": "Step", + "stop": "Arrêter", "stop-re-index-all": "Arrêter la Ré-indexation de Tout", "stopped": "Arrêté", "storage": "Stockage", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Personnalisez OpenMetadata avec votre logo d'entreprise, monogramme, favicon et couleur de marque.", "application-action-successfully": "Application {{action}} avec succès", "application-disabled-message": "Application actuellement désactivée. Cliques sur l'ellipse dans l'en-tête pour l'activer.", + "application-stop": "L'arrêt de l'application est en cours", "application-to-improve-data": "Améliorez vous données en utilisant les Applications pour MetaPilot, les Data Insights, et l'indexation de recherche.", "are-you-sure": "Êtes-vous sûr?", "are-you-sure-action-property": "Êtes-vous sûr de vouloir {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json index 29119f1ee0f..d666b6b877d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json @@ -1172,6 +1172,7 @@ "status": "Estado", "stay-up-to-date": "Mantente ao día", "step": "Paso", + "stop": "Parar", "stop-re-index-all": "Deteñer reindexación", "stopped": "Detido", "storage": "Almacenamento", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Personaliza OpenMetadata co logotipo da túa empresa, monograma, favicon e cor da marca.", "application-action-successfully": "A aplicación {{action}} correctamente", "application-disabled-message": "A aplicación está desactivada actualmente. Fai clic no menú de puntos suspensivos no encabezado para activala.", + "application-stop": "A parada da aplicación está en progreso", "application-to-improve-data": "Mellora os teus datos usando aplicacións para MetaPilot, Perspectivas de Datos, e Indexación de Busca", "are-you-sure": "Estás seguro?", "are-you-sure-action-property": "Estás seguro de que queres {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json index fccd116f469..d9b3df7c293 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json @@ -1172,6 +1172,7 @@ "status": "סטטוס", "stay-up-to-date": "הישאר מעודכן", "step": "Step", + "stop": "עצור", "stop-re-index-all": "עצור Re-Index", "stopped": "נעצר", "storage": "אחסון", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Customize OpenMetadata with your company logo, monogram, favicon and brand color.", "application-action-successfully": "האפליקציה {{action}} בהצלחה", "application-disabled-message": "האפליקציה מושבתת כרגע. לחץ על תפריט הנקודות התלתול בכותרת כדי להפעיל אותה.", + "application-stop": "עצירת היישום מתבצעת", "application-to-improve-data": "Improve your data using Applications for MetaPilot, Data Insights, and Search Indexing", "are-you-sure": "האם אתה בטוח?", "are-you-sure-action-property": "האם אתה בטוח שברצונך {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index 8d1affd39da..0c35b6a388c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -1172,6 +1172,7 @@ "status": "ステータス", "stay-up-to-date": "最新を維持", "step": "Step", + "stop": "停止", "stop-re-index-all": "Stop Re-Index", "stopped": "Stopped", "storage": "Storage", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Customize OpenMetadata with your company logo, monogram, favicon and brand color.", "application-action-successfully": "Application {{action}} successfully", "application-disabled-message": "Application is currently disabled. Click on the ellipsis menu in header to enable it.", + "application-stop": "アプリケーションの停止が進行中です", "application-to-improve-data": "Improve your data using Applications for MetaPilot, Data Insights, and Search Indexing", "are-you-sure": "よろしいですか?", "are-you-sure-action-property": "Are you sure you want to {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json index 01da1e07e78..278af4f3ddb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json @@ -1172,6 +1172,7 @@ "status": "Status", "stay-up-to-date": "Blijf Up-to-date", "step": "Step", + "stop": "Stop", "stop-re-index-all": "Stop herindexeren", "stopped": "Gestopt", "storage": "Storage", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Customize OpenMetadata with your company logo, monogram, favicon and brand color.", "application-action-successfully": "Applicatie {{action}} succesvol", "application-disabled-message": "De applicatie is momenteel uitgeschakeld. Klik op het ellipsismenu bovenin om het in te schakelen.", + "application-stop": "Het stoppen van de applicatie is aan de gang", "application-to-improve-data": "Verbeter je gegevens met behulp van toepassingen voor MetaPilot, data-inzichten en zoekindexering", "are-you-sure": "Weet u het zeker?", "are-you-sure-action-property": "Weet u zeker dat u {{action}} {{propertyName}} wilt?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json index 599bfc64247..2f6b7ff582a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json @@ -1172,6 +1172,7 @@ "status": "وضعیت", "stay-up-to-date": "در جریان بمانید", "step": "مرحله", + "stop": "توقف", "stop-re-index-all": "متوقف کردن دوباره ایندکس", "stopped": "متوقف شد", "storage": "ذخیره‌سازی", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "OpenMetadata را با لوگوی شرکت، مونوگرام، favicon و رنگ برند خود سفارشی کنید.", "application-action-successfully": "برنامه {{action}} با موفقیت انجام شد.", "application-disabled-message": "برنامه در حال حاضر غیرفعال است. برای فعال‌سازی آن روی منوی سه‌نقطه در سربرگ کلیک کنید.", + "application-stop": "توقف برنامه در حال انجام است", "application-to-improve-data": "داده‌های خود را با استفاده از برنامه‌ها برای MetaPilot، بینش داده و ایندکس جستجو بهبود دهید.", "are-you-sure": "آیا مطمئن هستید؟", "are-you-sure-action-property": "آیا مطمئن هستید که می‌خواهید {{action}} {{propertyName}}؟", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index 9aebad742e6..19e6dec16c9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -1172,6 +1172,7 @@ "status": "Status", "stay-up-to-date": "Mantenha-se Atualizado", "step": "Step", + "stop": "Parar", "stop-re-index-all": "Parar Reindexação", "stopped": "Parado", "storage": "Armazenamento", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Customize OpenMetadata with your company logo, monogram, favicon and brand color.", "application-action-successfully": "Aplicativo {{action}} com sucesso", "application-disabled-message": "O aplicativo está atualmente desativado. Clique no menu de reticências no cabeçalho para ativá-lo.", + "application-stop": "A parada do aplicativo está em andamento", "application-to-improve-data": "Aprimore seus dados utilizando Aplicações como MetaPilot, Data Insights e Indexação de Pesquisa", "are-you-sure": "Você tem certeza?", "are-you-sure-action-property": "Você tem certeza que deseja {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json index 783b7216887..6cce74819a4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json @@ -1172,6 +1172,7 @@ "status": "Status", "stay-up-to-date": "Mantenha-se Atualizado", "step": "Step", + "stop": "Parar", "stop-re-index-all": "Parar Reindexação", "stopped": "Parado", "storage": "Armazenamento", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Customize OpenMetadata with your company logo, monogram, favicon and brand color.", "application-action-successfully": "Aplicativo {{action}} com sucesso", "application-disabled-message": "O aplicativo está atualmente desativado. Clique no menu de reticências no cabeçalho para ativá-lo.", + "application-stop": "A paragem da aplicação está em progresso", "application-to-improve-data": "Aprimore seus dados utilizando Aplicações como MetaPilot, Data Insights e Indexação de Pesquisa", "are-you-sure": "Você tem certeza?", "are-you-sure-action-property": "Você tem certeza que deseja {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json index 7b355e170ba..3d3c8d34257 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json @@ -1172,6 +1172,7 @@ "status": "Статус", "stay-up-to-date": "Будьте в курсе последних событий", "step": "Step", + "stop": "Остановить", "stop-re-index-all": "Остановить ре-индексирование", "stopped": "Остановлено", "storage": "Хранилище", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "Customize OpenMetadata with your company logo, monogram, favicon and brand color.", "application-action-successfully": "Application {{action}} successfully", "application-disabled-message": "Application is currently disabled. Click on the ellipsis menu in header to enable it.", + "application-stop": "Остановка приложения выполняется", "application-to-improve-data": "Improve your data using Applications for MetaPilot, Data Insights, and Search Indexing", "are-you-sure": "Вы уверены?", "are-you-sure-action-property": "Are you sure you want to {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json index e12a3621fbe..e58f19d6ddf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json @@ -1172,6 +1172,7 @@ "status": "สถานะ", "stay-up-to-date": "อัปเดตอยู่เสมอ", "step": "ขั้นตอน", + "stop": "หยุด", "stop-re-index-all": "หยุดการสร้างดัชนีใหม่ทั้งหมด", "stopped": "หยุดแล้ว", "storage": "ที่จัดเก็บ", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "ปรับแต่ง OpenMetadata ด้วยโลโก้ของบริษัท, ตัวอักษร, favicon และสีแบรนด์", "application-action-successfully": "แอปพลิเคชัน {{action}} สำเร็จ", "application-disabled-message": "แอปพลิเคชันถูกปิดการใช้งานในขณะนี้ คลิกที่เมนูสามจุดในหัวข้อเพื่อเปิดใช้งาน", + "application-stop": "การหยุดแอปพลิเคชันกำลังดำเนินการ", "application-to-improve-data": "ปรับปรุงข้อมูลของคุณโดยใช้แอปพลิเคชันสำหรับ MetaPilot, ข้อมูลเชิงลึก, และการจัดทำดัชนีการค้นหา", "are-you-sure": "คุณแน่ใจหรือไม่?", "are-you-sure-action-property": "คุณแน่ใจหรือไม่ว่าต้องการ {{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index 85a332f607c..03457edf96d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -1172,6 +1172,7 @@ "status": "状态", "stay-up-to-date": "保持最新", "step": "步骤", + "stop": "停止", "stop-re-index-all": "停止重新索引", "stopped": "已停止", "storage": "存储", @@ -1430,6 +1431,7 @@ "appearance-configuration-message": "用您公司的徽标、字母图案、图标和品牌颜色自定义 OpenMetadata", "application-action-successfully": "应用{{action}}成功", "application-disabled-message": "应用程序目前处于禁用状态, 点击标题中的省略号菜单即可启用", + "application-stop": "应用程序停止进行中", "application-to-improve-data": "使用 MetaPilot、数据洞察和搜索索引应用改进数据", "are-you-sure": "您确定吗?", "are-you-sure-action-property": "您确定要{{action}} {{propertyName}}?", diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/applicationAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/applicationAPI.ts index 4b84e71786a..1a2f5d636ed 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/applicationAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/applicationAPI.ts @@ -135,3 +135,7 @@ export const restoreApp = async (id: string) => { return response.data; }; + +export const stopApp = async (name: string) => { + return await APIClient.post(`${BASE_URL}/stop/${getEncodedFqn(name)}`); +};