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'))));
-- 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;

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_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.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();

View File

@ -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<? extends NativeApplication> clz =
(Class<? extends NativeApplication>) 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:

View File

@ -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<Integer> {
.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")))

View File

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

View File

@ -43,7 +43,7 @@
"searchIndexMappingLanguage": "EN"
},
"appSchedule": {
"scheduleType": "Custom",
"scheduleTimeline": "Custom",
"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.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<App, CreateApp> {
return new CreateApp()
.withName(appMarketPlaceDefinition.getName())
.withAppConfiguration(appMarketPlaceDefinition.getAppConfiguration())
.withAppSchedule(new AppSchedule().withScheduleType(AppSchedule.ScheduleTimeline.HOURLY));
.withAppSchedule(new AppSchedule().withScheduleTimeline(ScheduleTimeline.HOURLY));
}
@Test

View File

@ -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": {

View File

@ -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 } : {}),
},
};

View File

@ -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',

View File

@ -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 = ({
<Col className="flex-col" flex="auto">
{appData.appSchedule && (
<>
<div>
<Space size={8}>
<Typography.Text className="right-panel-label">
{t('label.schedule-type')}
</Typography.Text>
<Typography.Text className="font-medium">
{(appData.appSchedule as AppScheduleClass).scheduleType ??
''}
</Typography.Text>
</Space>
<div className="d-flex items-center gap-2">
<Typography.Text className="right-panel-label">
{t('label.schedule-type')}
</Typography.Text>
<Typography.Text className="font-medium">
{(appData.appSchedule as AppScheduleClass).scheduleTimeline ??
''}
</Typography.Text>
</div>
<div>
<Space size={8}>
{!isEmpty(cronString) && (
<div className="d-flex items-center gap-2">
<Typography.Text className="right-panel-label">
{t('label.schedule-interval')}
</Typography.Text>
@ -171,8 +170,8 @@ const AppSchedule = ({
data-testid="cron-string">
{cronString}
</Typography.Text>
</Space>
</div>
</div>
)}
</>
)}
</Col>
@ -231,7 +230,7 @@ const AppSchedule = ({
}}
includePeriodOptions={initialOptions}
initialData={
(appData.appSchedule as AppScheduleClass)?.cronExpression ?? ''
(appData.appSchedule as AppScheduleClass)?.cronExpression
}
isLoading={isSaveLoading}
onCancel={onDialogCancel}

View File

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

View File

@ -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,

View File

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