Application cannot be installed with None as schedule type (#15524)

* Application cannot be installed with None as schedule type

* MINOR: Add / Fix GCS and ADLS - docs, bugs (#15502)

Add GCS and ADLS docs

* Add stack trace while throwing an error to debug (#15522)

* Fix #15533: Fix name & display name for kafka json schema parser (#15534)

* MINOR: postgres add ssl options in yaml (#15538)

* Small change to fix the Glossary TErm Tasks not fetching Owner automatically (#15535)

* remove the DataInsightAlert spec as the alerts test are covered in ObservabilityAlerts spec (#15531)

* MINOR: fix help dropdown item font and icon sizes (#15511)

* fix help dropdown item font and icon sizes

* added unit test for navbarUtils

* fix sonar failure

* changes as per comments

* minor fixes

* Minor: fixed DQ edit test case issue and searchIndexDetails typescript issue (#15528)

* Minor: fixed DQ edit test case issue and searchIndexDetails typescript issue

* fixed failing unit test

* fixed unit test for Data quality test

* reverting e2eLabeler changes

* chore(ui): separate routes as per categories (#15512)

* chore(ui): seprate routes as per categories

* add tests

* fix test

* domain path fix

* [MINOR] GX logging hierarchy (#15542)

* fix: GX module logging hierarchy

* style: ran python linting

* - Use Entity Type (#15546)

* removed docs 1.4.x (#15550)

* Suggestions Alert new design (#15532)

* update ux of suggestions alert

* locales

* minor changes

* fix descriptions

* minor css fixes

* Fix #11868: Duplicated queries cannot be created (#15519)

* Fix #11868: Duplicate query should throw an error of entityExists

* Fix #11868: Duplicate query should throw an error of entityExists

* fix test

* fix test

* Fix uniquee constraint for checksum in Postgres

---------

Co-authored-by: Pere Miquel Brull <peremiquelbrull@gmail.com>

* added OpenMetadataApplication.getDao (#15549)

* Add the entityUrl to the header for ThreadMessages (#15552)

* feat: AppResource (#15555)

forbid modification of system app

* MINOR: Skip source hash generation for service (#15516)

* sync the documentation roadmap page with the getcollate roadmap (#15551)

* Application cannot be installed with None as schedule type

* None type should not register in scheduler

* scheduleTimeline fix

* fix payload of schedule type none and change it to scheduleTimeline

* fix unit test

---------

Co-authored-by: Ashish Gupta <ashish@getcollate.io>
Co-authored-by: Ayush Shah <ayush@getcollate.io>
Co-authored-by: Mayur Singal <39544459+ulixius9@users.noreply.github.com>
Co-authored-by: IceS2 <pjt1991@gmail.com>
Co-authored-by: Aniket Katkar <aniketkatkar97@gmail.com>
Co-authored-by: Shailesh Parmar <shailesh.parmar.webdev@gmail.com>
Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
Co-authored-by: Teddy <teddy.crepineau@gmail.com>
Co-authored-by: Mohit Yadav <105265192+mohityadav766@users.noreply.github.com>
Co-authored-by: Imri Paran <imri.paran@gmail.com>
Co-authored-by: Karan Hotchandani <33024356+karanh37@users.noreply.github.com>
Co-authored-by: Pere Miquel Brull <peremiquelbrull@gmail.com>
Co-authored-by: mohitdeuex <mohit.y@deuexsolutions.com>
This commit is contained in:
Sriharsha Chintalapani 2024-03-19 05:53:25 -07:00 committed by GitHub
parent b52fdaf05e
commit 8377128c93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 80 additions and 41 deletions

View File

@ -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')))); UPDATE query_entity SET json = JSON_INSERT(json, '$.checksum', MD5(JSON_UNQUOTE(JSON_EXTRACT(json, '$.checksum'))));
-- Restructure dbServiceNames in ingestion_pipeline_entity -- Restructure dbServiceNames in ingestion_pipeline_entity
update ingestion_pipeline_entity set json = update ingestion_pipeline_entity set json =
JSON_INSERT( 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 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_type(suggestionType);
ALTER TABLE suggestions ADD INDEX index_suggestions_status(status); 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;

View File

@ -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_type ON suggestions (suggestionType);
CREATE INDEX index_suggestions_status ON suggestions (status); 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;

View File

@ -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.App;
import org.openmetadata.schema.entity.app.AppRunRecord; import org.openmetadata.schema.entity.app.AppRunRecord;
import org.openmetadata.schema.entity.app.AppType; 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.ScheduleType;
import org.openmetadata.schema.entity.app.ScheduledExecutionContext; import org.openmetadata.schema.entity.app.ScheduledExecutionContext;
import org.openmetadata.schema.entity.applications.configuration.ApplicationConfig; import org.openmetadata.schema.entity.applications.configuration.ApplicationConfig;
@ -61,6 +62,11 @@ public class AbstractNativeApplication implements NativeApplication {
@Override @Override
public void install() { 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 if (app.getAppType() == AppType.Internal
&& app.getScheduleType().equals(ScheduleType.Scheduled)) { && app.getScheduleType().equals(ScheduleType.Scheduled)) {
scheduleInternal(); scheduleInternal();

View File

@ -19,6 +19,7 @@ import org.openmetadata.schema.AppRuntime;
import org.openmetadata.schema.entity.app.App; import org.openmetadata.schema.entity.app.App;
import org.openmetadata.schema.entity.app.AppRunType; import org.openmetadata.schema.entity.app.AppRunType;
import org.openmetadata.schema.entity.app.AppSchedule; import org.openmetadata.schema.entity.app.AppSchedule;
import org.openmetadata.schema.entity.app.ScheduleTimeline;
import org.openmetadata.service.OpenMetadataApplicationConfig; import org.openmetadata.service.OpenMetadataApplicationConfig;
import org.openmetadata.service.apps.NativeApplication; import org.openmetadata.service.apps.NativeApplication;
import org.openmetadata.service.exception.UnhandledServerException; import org.openmetadata.service.exception.UnhandledServerException;
@ -142,8 +143,14 @@ public class AppScheduler {
AppRuntime context = getAppRuntime(application); AppRuntime context = getAppRuntime(application);
if (Boolean.TRUE.equals(context.getEnabled())) { if (Boolean.TRUE.equals(context.getEnabled())) {
JobDetail jobDetail = jobBuilder(application, application.getName()); JobDetail jobDetail = jobBuilder(application, application.getName());
Trigger trigger = trigger(application); if (!application
scheduler.scheduleJob(jobDetail, trigger); .getAppSchedule()
.getScheduleTimeline()
.value()
.equals(ScheduleTimeline.NONE)) {
Trigger trigger = trigger(application);
scheduler.scheduleJob(jobDetail, trigger);
}
} else { } else {
LOG.info("[Applications] App cannot be scheduled since it is disabled"); 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 { private JobDetail jobBuilder(App app, String jobIdentity) throws ClassNotFoundException {
JobDataMap dataMap = new JobDataMap(); JobDataMap dataMap = new JobDataMap();
dataMap.put(APP_INFO_KEY, JsonUtils.pojoToJson(app)); dataMap.put(APP_INFO_KEY, JsonUtils.pojoToJson(app));
dataMap.put("triggerType", AppRunType.Scheduled.value()); dataMap.put("triggerType", app.getAppSchedule().getScheduleTimeline().value());
Class<? extends NativeApplication> clz = Class<? extends NativeApplication> clz =
(Class<? extends NativeApplication>) Class.forName(app.getClassName()); (Class<? extends NativeApplication>) Class.forName(app.getClassName());
JobBuilder jobBuilder = JobBuilder jobBuilder =
@ -196,7 +203,7 @@ public class AppScheduler {
} }
public static CronScheduleBuilder getCronSchedule(AppSchedule scheduleInfo) { public static CronScheduleBuilder getCronSchedule(AppSchedule scheduleInfo) {
switch (scheduleInfo.getScheduleType()) { switch (scheduleInfo.getScheduleTimeline()) {
case HOURLY: case HOURLY:
return CronScheduleBuilder.cronSchedule("0 0 * ? * *"); return CronScheduleBuilder.cronSchedule("0 0 * ? * *");
case DAILY: case DAILY:

View File

@ -39,6 +39,7 @@ import org.openmetadata.schema.EntityInterface;
import org.openmetadata.schema.ServiceEntityInterface; import org.openmetadata.schema.ServiceEntityInterface;
import org.openmetadata.schema.entity.app.App; import org.openmetadata.schema.entity.app.App;
import org.openmetadata.schema.entity.app.AppSchedule; 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.app.ScheduledExecutionContext;
import org.openmetadata.schema.entity.services.ingestionPipelines.IngestionPipeline; import org.openmetadata.schema.entity.services.ingestionPipelines.IngestionPipeline;
import org.openmetadata.schema.services.connections.metadata.OpenMetadataConnection; import org.openmetadata.schema.services.connections.metadata.OpenMetadataConnection;
@ -254,8 +255,7 @@ public class OpenMetadataOperations implements Callable<Integer> {
.withId(UUID.randomUUID()) .withId(UUID.randomUUID())
.withName("SearchIndexApp") .withName("SearchIndexApp")
.withClassName("org.openmetadata.service.apps.bundles.searchIndex.SearchIndexApp") .withClassName("org.openmetadata.service.apps.bundles.searchIndex.SearchIndexApp")
.withAppSchedule( .withAppSchedule(new AppSchedule().withScheduleTimeline(ScheduleTimeline.DAILY))
new AppSchedule().withScheduleType(AppSchedule.ScheduleTimeline.DAILY))
.withAppConfiguration( .withAppConfiguration(
new EventPublisherJob() new EventPublisherJob()
.withEntities(new HashSet<>(List.of("all"))) .withEntities(new HashSet<>(List.of("all")))

View File

@ -3,7 +3,7 @@
"displayName": "Data Insights", "displayName": "Data Insights",
"appConfiguration": {}, "appConfiguration": {},
"appSchedule": { "appSchedule": {
"scheduleType": "Custom", "scheduleTimeline": "Custom",
"cronExpression": "0 0 1/1 * *" "cronExpression": "0 0 1/1 * *"
} }
} }

View File

@ -43,7 +43,7 @@
"searchIndexMappingLanguage": "EN" "searchIndexMappingLanguage": "EN"
}, },
"appSchedule": { "appSchedule": {
"scheduleType": "Custom", "scheduleTimeline": "Custom",
"cronExpression": "0 0 * * *" "cronExpression": "0 0 * * *"
} }
} }

View File

@ -15,6 +15,7 @@ import org.openmetadata.schema.entity.app.AppMarketPlaceDefinition;
import org.openmetadata.schema.entity.app.AppSchedule; import org.openmetadata.schema.entity.app.AppSchedule;
import org.openmetadata.schema.entity.app.CreateApp; import org.openmetadata.schema.entity.app.CreateApp;
import org.openmetadata.schema.entity.app.CreateAppMarketPlaceDefinitionReq; import org.openmetadata.schema.entity.app.CreateAppMarketPlaceDefinitionReq;
import org.openmetadata.schema.entity.app.ScheduleTimeline;
import org.openmetadata.service.Entity; import org.openmetadata.service.Entity;
import org.openmetadata.service.exception.EntityNotFoundException; import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.resources.EntityResourceTest; import org.openmetadata.service.resources.EntityResourceTest;
@ -51,7 +52,7 @@ public class AppsResourceTest extends EntityResourceTest<App, CreateApp> {
return new CreateApp() return new CreateApp()
.withName(appMarketPlaceDefinition.getName()) .withName(appMarketPlaceDefinition.getName())
.withAppConfiguration(appMarketPlaceDefinition.getAppConfiguration()) .withAppConfiguration(appMarketPlaceDefinition.getAppConfiguration())
.withAppSchedule(new AppSchedule().withScheduleType(AppSchedule.ScheduleTimeline.HOURLY)); .withAppSchedule(new AppSchedule().withScheduleTimeline(ScheduleTimeline.HOURLY));
} }
@Test @Test

View File

@ -25,15 +25,17 @@
] ]
}, },
"scheduleTimeline": { "scheduleTimeline": {
"javaType": "org.openmetadata.schema.entity.app.ScheduleTimeline",
"description": "This schema defines the Application ScheduleTimeline Options",
"type": "string", "type": "string",
"enum": ["Hourly"," Daily", "Weekly", "Monthly", "Custom"], "enum": ["Hourly"," Daily", "Weekly", "Monthly", "Custom", "None"],
"default": "Weekly" "default": "Weekly"
}, },
"appSchedule": { "appSchedule": {
"javaType": "org.openmetadata.schema.entity.app.AppSchedule", "javaType": "org.openmetadata.schema.entity.app.AppSchedule",
"description": "This schema defines the type of application.", "description": "This schema defines the type of application.",
"properties": { "properties": {
"scheduleType": { "scheduleTimeline": {
"$ref": "#/definitions/scheduleTimeline" "$ref": "#/definitions/scheduleTimeline"
}, },
"cronExpression": { "cronExpression": {
@ -41,7 +43,7 @@
"type": "string" "type": "string"
} }
}, },
"required": ["scheduleType"], "required": ["scheduleTimeline"],
"additionalProperties": false "additionalProperties": false
}, },
"appType": { "appType": {

View File

@ -32,7 +32,7 @@ import {
import { ItemType } from 'antd/lib/menu/hooks/useItems'; import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { noop } from 'lodash'; import { isEmpty, noop } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@ -273,8 +273,10 @@ const AppDetails = () => {
const updatedData = { const updatedData = {
...appData, ...appData,
appSchedule: { appSchedule: {
scheduleType: ScheduleTimeline.Custom, scheduleTimeline: isEmpty(cron)
cronExpression: cron, ? ScheduleTimeline.None
: ScheduleTimeline.Custom,
...(cron ? { cronExpression: cron } : {}),
}, },
}; };

View File

@ -58,7 +58,7 @@ const mockProps1 = {
}, },
}, },
scheduleInfo: { scheduleInfo: {
scheduleType: ScheduleTimeline.Custom, scheduleTimeline: ScheduleTimeline.Custom,
cronExpression: '0 0 0 1/1 * ? *', cronExpression: '0 0 0 1/1 * ? *',
}, },
id: '6e4d3dcf-238d-4874-b4e4-dd863ede6544-OnDemand-1706871884587', id: '6e4d3dcf-238d-4874-b4e4-dd863ede6544-OnDemand-1706871884587',

View File

@ -12,6 +12,7 @@
*/ */
import { Button, Col, Divider, Modal, Row, Space, Typography } from 'antd'; import { Button, Col, Divider, Modal, Row, Space, Typography } from 'antd';
import cronstrue from 'cronstrue'; import cronstrue from 'cronstrue';
import { isEmpty } from 'lodash';
import React, { import React, {
useCallback, useCallback,
useEffect, useEffect,
@ -68,11 +69,10 @@ const AppSchedule = ({
}, [appData]); }, [appData]);
const cronString = useMemo(() => { const cronString = useMemo(() => {
if (appData.appSchedule) { const cronExpression = (appData.appSchedule as AppScheduleClass)
const cronExp = ?.cronExpression;
(appData.appSchedule as AppScheduleClass).cronExpression ?? ''; if (cronExpression) {
return cronstrue.toString(cronExpression, {
return cronstrue.toString(cronExp, {
throwExceptionOnParseError: false, throwExceptionOnParseError: false,
}); });
} }
@ -150,19 +150,18 @@ const AppSchedule = ({
<Col className="flex-col" flex="auto"> <Col className="flex-col" flex="auto">
{appData.appSchedule && ( {appData.appSchedule && (
<> <>
<div> <div className="d-flex items-center gap-2">
<Space size={8}> <Typography.Text className="right-panel-label">
<Typography.Text className="right-panel-label"> {t('label.schedule-type')}
{t('label.schedule-type')} </Typography.Text>
</Typography.Text> <Typography.Text className="font-medium">
<Typography.Text className="font-medium"> {(appData.appSchedule as AppScheduleClass).scheduleTimeline ??
{(appData.appSchedule as AppScheduleClass).scheduleType ?? ''}
''} </Typography.Text>
</Typography.Text>
</Space>
</div> </div>
<div>
<Space size={8}> {!isEmpty(cronString) && (
<div className="d-flex items-center gap-2">
<Typography.Text className="right-panel-label"> <Typography.Text className="right-panel-label">
{t('label.schedule-interval')} {t('label.schedule-interval')}
</Typography.Text> </Typography.Text>
@ -171,8 +170,8 @@ const AppSchedule = ({
data-testid="cron-string"> data-testid="cron-string">
{cronString} {cronString}
</Typography.Text> </Typography.Text>
</Space> </div>
</div> )}
</> </>
)} )}
</Col> </Col>
@ -231,7 +230,7 @@ const AppSchedule = ({
}} }}
includePeriodOptions={initialOptions} includePeriodOptions={initialOptions}
initialData={ initialData={
(appData.appSchedule as AppScheduleClass)?.cronExpression ?? '' (appData.appSchedule as AppScheduleClass)?.cronExpression
} }
isLoading={isSaveLoading} isLoading={isSaveLoading}
onCancel={onDialogCancel} onCancel={onDialogCancel}

View File

@ -97,7 +97,7 @@ export const mockApplicationData = {
}, },
pipelines: [], pipelines: [],
appSchedule: { appSchedule: {
scheduleType: ScheduleTimeline.Custom, scheduleTimeline: ScheduleTimeline.Custom,
cronExpression: '0 0 0 1/1 * ? *', cronExpression: '0 0 0 1/1 * ? *',
}, },
appScreenshots: ['SearchIndexPic1.png'], appScreenshots: ['SearchIndexPic1.png'],

View File

@ -15,6 +15,7 @@ import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8'; import validator from '@rjsf/validator-ajv8';
import { Col, Row, Typography } from 'antd'; import { Col, Row, Typography } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@ -118,8 +119,10 @@ const AppInstall = () => {
const data: CreateAppRequest = { const data: CreateAppRequest = {
appConfiguration: appConfiguration ?? appData?.appConfiguration, appConfiguration: appConfiguration ?? appData?.appConfiguration,
appSchedule: { appSchedule: {
scheduleType: ScheduleTimeline.Custom, scheduleTimeline: isEmpty(repeatFrequency)
cronExpression: repeatFrequency, ? ScheduleTimeline.None
: ScheduleTimeline.Custom,
...(repeatFrequency ? { cronExpression: repeatFrequency } : {}),
}, },
name: fqn, name: fqn,
description: appData?.description, description: appData?.description,

View File

@ -295,7 +295,9 @@ const LogsViewer = () => {
return { return {
Type: Type:
ingestionDetails?.pipelineType ?? scheduleClass?.scheduleType ?? '--', ingestionDetails?.pipelineType ??
scheduleClass?.scheduleTimeline ??
'--',
Schedule: Schedule:
ingestionDetails?.airflowConfig.scheduleInterval ?? ingestionDetails?.airflowConfig.scheduleInterval ??
scheduleClass?.cronExpression ?? scheduleClass?.cronExpression ??