diff --git a/bootstrap/sql/migrations/native/1.4.0/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.4.0/mysql/schemaChanges.sql index 9097308f77d..2fff714f4e1 100644 --- a/bootstrap/sql/migrations/native/1.4.0/mysql/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.4.0/mysql/schemaChanges.sql @@ -7,7 +7,6 @@ ALTER TABLE query_entity ADD COLUMN checksum VARCHAR(32) GENERATED ALWAYS AS (js UPDATE query_entity SET json = JSON_INSERT(json, '$.checksum', MD5(JSON_UNQUOTE(JSON_EXTRACT(json, '$.checksum')))); - -- Restructure dbServiceNames in ingestion_pipeline_entity update ingestion_pipeline_entity set json = JSON_INSERT( @@ -68,3 +67,12 @@ ALTER TABLE user_entity ADD INDEX index_user_entity_deleted(nameHash, deleted); ALTER TABLE apps_extension_time_series ADD INDEX apps_extension_time_series_index(appId); ALTER TABLE suggestions ADD INDEX index_suggestions_type(suggestionType); ALTER TABLE suggestions ADD INDEX index_suggestions_status(status); + +-- Change scheduleType to scheduleTimeline +UPDATE installed_apps +SET json = JSON_INSERT( + JSON_REMOVE(json, '$.appSchedule.scheduleType'), + '$.appSchedule.scheduleTimeline', + JSON_EXTRACT(json, '$.appSchedule.scheduleType') + ); +delete from apps_extension_time_series; diff --git a/bootstrap/sql/migrations/native/1.4.0/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.4.0/postgres/schemaChanges.sql index b90824db019..1524842c801 100644 --- a/bootstrap/sql/migrations/native/1.4.0/postgres/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.4.0/postgres/schemaChanges.sql @@ -67,3 +67,12 @@ CREATE INDEX apps_extension_time_series_index ON apps_extension_time_series (app CREATE INDEX index_suggestions_type ON suggestions (suggestionType); CREATE INDEX index_suggestions_status ON suggestions (status); +-- change scheduleType to scheduleTimeline +UPDATE installed_apps +SET json = jsonb_set( + json::jsonb #- '{appSchedule,scheduleType}', + '{appSchedule,scheduleTimeline}', + (json #> '{appSchedule,scheduleType}')::jsonb, + true + ); +delete from apps_extension_time_series; 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 02edefe7a56..aa4ece63680 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 @@ -13,6 +13,7 @@ import org.openmetadata.schema.api.services.ingestionPipelines.CreateIngestionPi import org.openmetadata.schema.entity.app.App; import org.openmetadata.schema.entity.app.AppRunRecord; import org.openmetadata.schema.entity.app.AppType; +import org.openmetadata.schema.entity.app.ScheduleTimeline; import org.openmetadata.schema.entity.app.ScheduleType; import org.openmetadata.schema.entity.app.ScheduledExecutionContext; import org.openmetadata.schema.entity.applications.configuration.ApplicationConfig; @@ -61,6 +62,11 @@ public class AbstractNativeApplication implements NativeApplication { @Override public void install() { + // If the app does not have any Schedule Return without scheduling + if (app.getAppSchedule() != null + && app.getAppSchedule().getScheduleTimeline().equals(ScheduleTimeline.NONE)) { + return; + } if (app.getAppType() == AppType.Internal && app.getScheduleType().equals(ScheduleType.Scheduled)) { scheduleInternal(); 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 7c6affad96c..2908783d71b 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 @@ -19,6 +19,7 @@ import org.openmetadata.schema.AppRuntime; import org.openmetadata.schema.entity.app.App; import org.openmetadata.schema.entity.app.AppRunType; import org.openmetadata.schema.entity.app.AppSchedule; +import org.openmetadata.schema.entity.app.ScheduleTimeline; import org.openmetadata.service.OpenMetadataApplicationConfig; import org.openmetadata.service.apps.NativeApplication; import org.openmetadata.service.exception.UnhandledServerException; @@ -142,8 +143,14 @@ public class AppScheduler { AppRuntime context = getAppRuntime(application); if (Boolean.TRUE.equals(context.getEnabled())) { JobDetail jobDetail = jobBuilder(application, application.getName()); - Trigger trigger = trigger(application); - scheduler.scheduleJob(jobDetail, trigger); + if (!application + .getAppSchedule() + .getScheduleTimeline() + .value() + .equals(ScheduleTimeline.NONE)) { + Trigger trigger = trigger(application); + scheduler.scheduleJob(jobDetail, trigger); + } } else { LOG.info("[Applications] App cannot be scheduled since it is disabled"); } @@ -171,7 +178,7 @@ public class AppScheduler { private JobDetail jobBuilder(App app, String jobIdentity) throws ClassNotFoundException { JobDataMap dataMap = new JobDataMap(); dataMap.put(APP_INFO_KEY, JsonUtils.pojoToJson(app)); - dataMap.put("triggerType", AppRunType.Scheduled.value()); + dataMap.put("triggerType", app.getAppSchedule().getScheduleTimeline().value()); Class clz = (Class) Class.forName(app.getClassName()); JobBuilder jobBuilder = @@ -196,7 +203,7 @@ public class AppScheduler { } public static CronScheduleBuilder getCronSchedule(AppSchedule scheduleInfo) { - switch (scheduleInfo.getScheduleType()) { + switch (scheduleInfo.getScheduleTimeline()) { case HOURLY: return CronScheduleBuilder.cronSchedule("0 0 * ? * *"); case DAILY: diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/util/OpenMetadataOperations.java b/openmetadata-service/src/main/java/org/openmetadata/service/util/OpenMetadataOperations.java index c9ff047b7f9..56c60c1268d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/util/OpenMetadataOperations.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/util/OpenMetadataOperations.java @@ -39,6 +39,7 @@ import org.openmetadata.schema.EntityInterface; import org.openmetadata.schema.ServiceEntityInterface; import org.openmetadata.schema.entity.app.App; import org.openmetadata.schema.entity.app.AppSchedule; +import org.openmetadata.schema.entity.app.ScheduleTimeline; import org.openmetadata.schema.entity.app.ScheduledExecutionContext; import org.openmetadata.schema.entity.services.ingestionPipelines.IngestionPipeline; import org.openmetadata.schema.services.connections.metadata.OpenMetadataConnection; @@ -254,8 +255,7 @@ public class OpenMetadataOperations implements Callable { .withId(UUID.randomUUID()) .withName("SearchIndexApp") .withClassName("org.openmetadata.service.apps.bundles.searchIndex.SearchIndexApp") - .withAppSchedule( - new AppSchedule().withScheduleType(AppSchedule.ScheduleTimeline.DAILY)) + .withAppSchedule(new AppSchedule().withScheduleTimeline(ScheduleTimeline.DAILY)) .withAppConfiguration( new EventPublisherJob() .withEntities(new HashSet<>(List.of("all"))) diff --git a/openmetadata-service/src/main/resources/json/data/app/DataInsightsApplication.json b/openmetadata-service/src/main/resources/json/data/app/DataInsightsApplication.json index 590d467790a..4e3c99da6b7 100644 --- a/openmetadata-service/src/main/resources/json/data/app/DataInsightsApplication.json +++ b/openmetadata-service/src/main/resources/json/data/app/DataInsightsApplication.json @@ -3,7 +3,7 @@ "displayName": "Data Insights", "appConfiguration": {}, "appSchedule": { - "scheduleType": "Custom", + "scheduleTimeline": "Custom", "cronExpression": "0 0 1/1 * *" } } \ No newline at end of file diff --git a/openmetadata-service/src/main/resources/json/data/app/SearchIndexingApplication.json b/openmetadata-service/src/main/resources/json/data/app/SearchIndexingApplication.json index 179fc674cdd..a373325802e 100644 --- a/openmetadata-service/src/main/resources/json/data/app/SearchIndexingApplication.json +++ b/openmetadata-service/src/main/resources/json/data/app/SearchIndexingApplication.json @@ -43,7 +43,7 @@ "searchIndexMappingLanguage": "EN" }, "appSchedule": { - "scheduleType": "Custom", + "scheduleTimeline": "Custom", "cronExpression": "0 0 * * *" } } \ No newline at end of file diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/apps/AppsResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/apps/AppsResourceTest.java index 86efc8d0a83..940bbcb3f04 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/apps/AppsResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/apps/AppsResourceTest.java @@ -15,6 +15,7 @@ import org.openmetadata.schema.entity.app.AppMarketPlaceDefinition; import org.openmetadata.schema.entity.app.AppSchedule; import org.openmetadata.schema.entity.app.CreateApp; import org.openmetadata.schema.entity.app.CreateAppMarketPlaceDefinitionReq; +import org.openmetadata.schema.entity.app.ScheduleTimeline; import org.openmetadata.service.Entity; import org.openmetadata.service.exception.EntityNotFoundException; import org.openmetadata.service.resources.EntityResourceTest; @@ -51,7 +52,7 @@ public class AppsResourceTest extends EntityResourceTest { return new CreateApp() .withName(appMarketPlaceDefinition.getName()) .withAppConfiguration(appMarketPlaceDefinition.getAppConfiguration()) - .withAppSchedule(new AppSchedule().withScheduleType(AppSchedule.ScheduleTimeline.HOURLY)); + .withAppSchedule(new AppSchedule().withScheduleTimeline(ScheduleTimeline.HOURLY)); } @Test diff --git a/openmetadata-spec/src/main/resources/json/schema/entity/applications/app.json b/openmetadata-spec/src/main/resources/json/schema/entity/applications/app.json index 02d74906bbb..a31042d4127 100644 --- a/openmetadata-spec/src/main/resources/json/schema/entity/applications/app.json +++ b/openmetadata-spec/src/main/resources/json/schema/entity/applications/app.json @@ -25,15 +25,17 @@ ] }, "scheduleTimeline": { + "javaType": "org.openmetadata.schema.entity.app.ScheduleTimeline", + "description": "This schema defines the Application ScheduleTimeline Options", "type": "string", - "enum": ["Hourly"," Daily", "Weekly", "Monthly", "Custom"], + "enum": ["Hourly"," Daily", "Weekly", "Monthly", "Custom", "None"], "default": "Weekly" }, "appSchedule": { "javaType": "org.openmetadata.schema.entity.app.AppSchedule", "description": "This schema defines the type of application.", "properties": { - "scheduleType": { + "scheduleTimeline": { "$ref": "#/definitions/scheduleTimeline" }, "cronExpression": { @@ -41,7 +43,7 @@ "type": "string" } }, - "required": ["scheduleType"], + "required": ["scheduleTimeline"], "additionalProperties": false }, "appType": { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx index faddf3e8c7b..80fa23e44ce 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/AppDetails.component.tsx @@ -32,7 +32,7 @@ import { import { ItemType } from 'antd/lib/menu/hooks/useItems'; import { AxiosError } from 'axios'; import { compare } from 'fast-json-patch'; -import { noop } from 'lodash'; +import { isEmpty, noop } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; @@ -273,8 +273,10 @@ const AppDetails = () => { const updatedData = { ...appData, appSchedule: { - scheduleType: ScheduleTimeline.Custom, - cronExpression: cron, + scheduleTimeline: isEmpty(cron) + ? ScheduleTimeline.None + : ScheduleTimeline.Custom, + ...(cron ? { cronExpression: cron } : {}), }, }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppLogsViewer/AppLogsViewer.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppLogsViewer/AppLogsViewer.test.tsx index 74619d8da2b..ede24d1c15f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppLogsViewer/AppLogsViewer.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppLogsViewer/AppLogsViewer.test.tsx @@ -58,7 +58,7 @@ const mockProps1 = { }, }, scheduleInfo: { - scheduleType: ScheduleTimeline.Custom, + scheduleTimeline: ScheduleTimeline.Custom, cronExpression: '0 0 0 1/1 * ? *', }, id: '6e4d3dcf-238d-4874-b4e4-dd863ede6544-OnDemand-1706871884587', diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppSchedule/AppSchedule.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppSchedule/AppSchedule.component.tsx index b8402f76671..ac7a7270c35 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppSchedule/AppSchedule.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppSchedule/AppSchedule.component.tsx @@ -12,6 +12,7 @@ */ import { Button, Col, Divider, Modal, Row, Space, Typography } from 'antd'; import cronstrue from 'cronstrue'; +import { isEmpty } from 'lodash'; import React, { useCallback, useEffect, @@ -68,11 +69,10 @@ const AppSchedule = ({ }, [appData]); const cronString = useMemo(() => { - if (appData.appSchedule) { - const cronExp = - (appData.appSchedule as AppScheduleClass).cronExpression ?? ''; - - return cronstrue.toString(cronExp, { + const cronExpression = (appData.appSchedule as AppScheduleClass) + ?.cronExpression; + if (cronExpression) { + return cronstrue.toString(cronExpression, { throwExceptionOnParseError: false, }); } @@ -150,19 +150,18 @@ const AppSchedule = ({ {appData.appSchedule && ( <> -
- - - {t('label.schedule-type')} - - - {(appData.appSchedule as AppScheduleClass).scheduleType ?? - ''} - - +
+ + {t('label.schedule-type')} + + + {(appData.appSchedule as AppScheduleClass).scheduleTimeline ?? + ''} +
-
- + + {!isEmpty(cronString) && ( +
{t('label.schedule-interval')} @@ -171,8 +170,8 @@ const AppSchedule = ({ data-testid="cron-string"> {cronString} - -
+
+ )} )} @@ -231,7 +230,7 @@ const AppSchedule = ({ }} includePeriodOptions={initialOptions} initialData={ - (appData.appSchedule as AppScheduleClass)?.cronExpression ?? '' + (appData.appSchedule as AppScheduleClass)?.cronExpression } isLoading={isSaveLoading} onCancel={onDialogCancel} diff --git a/openmetadata-ui/src/main/resources/ui/src/mocks/rests/applicationAPI.mock.ts b/openmetadata-ui/src/main/resources/ui/src/mocks/rests/applicationAPI.mock.ts index 57541af5262..0fd53a89801 100644 --- a/openmetadata-ui/src/main/resources/ui/src/mocks/rests/applicationAPI.mock.ts +++ b/openmetadata-ui/src/main/resources/ui/src/mocks/rests/applicationAPI.mock.ts @@ -97,7 +97,7 @@ export const mockApplicationData = { }, pipelines: [], appSchedule: { - scheduleType: ScheduleTimeline.Custom, + scheduleTimeline: ScheduleTimeline.Custom, cronExpression: '0 0 0 1/1 * ? *', }, appScreenshots: ['SearchIndexPic1.png'], diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AppInstall/AppInstall.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AppInstall/AppInstall.component.tsx index 3327a6e19a2..86062a5b2bf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/AppInstall/AppInstall.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AppInstall/AppInstall.component.tsx @@ -15,6 +15,7 @@ import { RJSFSchema } from '@rjsf/utils'; import validator from '@rjsf/validator-ajv8'; import { Col, Row, Typography } from 'antd'; import { AxiosError } from 'axios'; +import { isEmpty } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; @@ -118,8 +119,10 @@ const AppInstall = () => { const data: CreateAppRequest = { appConfiguration: appConfiguration ?? appData?.appConfiguration, appSchedule: { - scheduleType: ScheduleTimeline.Custom, - cronExpression: repeatFrequency, + scheduleTimeline: isEmpty(repeatFrequency) + ? ScheduleTimeline.None + : ScheduleTimeline.Custom, + ...(repeatFrequency ? { cronExpression: repeatFrequency } : {}), }, name: fqn, description: appData?.description, diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/LogsViewer/LogsViewer.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/LogsViewer/LogsViewer.component.tsx index d716729a7e0..7abaf6ebff1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/LogsViewer/LogsViewer.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/LogsViewer/LogsViewer.component.tsx @@ -295,7 +295,9 @@ const LogsViewer = () => { return { Type: - ingestionDetails?.pipelineType ?? scheduleClass?.scheduleType ?? '--', + ingestionDetails?.pipelineType ?? + scheduleClass?.scheduleTimeline ?? + '--', Schedule: ingestionDetails?.airflowConfig.scheduleInterval ?? scheduleClass?.cronExpression ??