diff --git a/example/app/data/DemoDataSource/plugins/job/helloWorldLocalContainerRunner.json b/example/app/data/DemoDataSource/plugins/job/helloWorldLocalContainerRunner.json new file mode 100644 index 000000000..dd52f15e8 --- /dev/null +++ b/example/app/data/DemoDataSource/plugins/job/helloWorldLocalContainerRunner.json @@ -0,0 +1,14 @@ +{ + "name": "helloWorldLocalContainerRunner", + "type": "JOBCORE:LocalContainer", + "label": "Hello world", + "image": { + "type": "JOBCORE:ContainerImage", + "description": "Hello world test container", + "imageName": "hello-world", + "version": "latest", + "registryName": "library" + }, + "network": "example_default", + "environmentVariables": [] +} diff --git a/example/app/data/DemoDataSource/plugins/job/reverseDescriptionRunner.json b/example/app/data/DemoDataSource/plugins/job/reverseDescriptionRunner.json new file mode 100644 index 000000000..6216ca7a8 --- /dev/null +++ b/example/app/data/DemoDataSource/plugins/job/reverseDescriptionRunner.json @@ -0,0 +1,4 @@ +{ + "name": "reverseDescriptionRunner", + "type": "JOBCORE:ReverseDescription" +} diff --git a/example/app/data/DemoDataSource/recipes/job.recipe.json b/example/app/data/DemoDataSource/recipes/job.recipe.json index 4e4b7d48a..a8bc44f47 100644 --- a/example/app/data/DemoDataSource/recipes/job.recipe.json +++ b/example/app/data/DemoDataSource/recipes/job.recipe.json @@ -108,7 +108,23 @@ "recipe": { "name": "job-control", "type": "CORE:UiRecipe", - "plugin": "@development-framework/dm-core-plugins/job/controls" + "plugin": "@development-framework/dm-core-plugins/job/controls", + "config": { + "type": "PLUGINS:dm-core-plugins/job/ControlConfig", + "recurring": false, + "runnerTemplates": [ + { + "type": "PLUGINS:dm-core-plugins/common/Template", + "path": "dmss://DemoDataSource/plugins/job/helloWorldLocalContainerRunner", + "label": "Hello world" + }, + { + "type": "PLUGINS:dm-core-plugins/common/Template", + "path": "dmss://DemoDataSource/plugins/job/reverseDescriptionRunner", + "label": "Reverse description" + } + ] + } } }, "gridArea": { diff --git a/example/app/data/DemoDataSource/recipes/plugins/list/task_list/task.recipe.json b/example/app/data/DemoDataSource/recipes/plugins/list/task_list/task.recipe.json index 6bc520347..ecafdf2c4 100644 --- a/example/app/data/DemoDataSource/recipes/plugins/list/task_list/task.recipe.json +++ b/example/app/data/DemoDataSource/recipes/plugins/list/task_list/task.recipe.json @@ -55,7 +55,7 @@ "type": "PLUGINS:dm-core-plugins/list/ListPluginConfig", "templates": [ { - "type": "PLUGINS:dm-core-plugins/table/Template", + "type": "PLUGINS:dm-core-plugins/common/Template", "path": "~._template_", "label": "Template1" } diff --git a/example/app/data/DemoDataSource/recipes/plugins/list/templates/task.recipe.json b/example/app/data/DemoDataSource/recipes/plugins/list/templates/task.recipe.json index ca72ec61f..1a7db3a82 100644 --- a/example/app/data/DemoDataSource/recipes/plugins/list/templates/task.recipe.json +++ b/example/app/data/DemoDataSource/recipes/plugins/list/templates/task.recipe.json @@ -55,12 +55,12 @@ "type": "PLUGINS:dm-core-plugins/list/ListPluginConfig", "templates": [ { - "type": "PLUGINS:dm-core-plugins/table/Template", + "type": "PLUGINS:dm-core-plugins/common/Template", "path": "~._templates_[0]", "label": "Template1" }, { - "type": "PLUGINS:dm-core-plugins/table/Template", + "type": "PLUGINS:dm-core-plugins/common/Template", "path": "~._templates_[1]", "label": "Template2" } diff --git a/example/app/data/DemoDataSource/recipes/plugins/table/car_list/carList.recipe.json b/example/app/data/DemoDataSource/recipes/plugins/table/car_list/carList.recipe.json index 3fe50896f..ec9a2d364 100644 --- a/example/app/data/DemoDataSource/recipes/plugins/table/car_list/carList.recipe.json +++ b/example/app/data/DemoDataSource/recipes/plugins/table/car_list/carList.recipe.json @@ -80,17 +80,17 @@ "type": "PLUGINS:dm-core-plugins/table/TablePluginConfig", "templates": [ { - "type": "PLUGINS:dm-core-plugins/table/Template", + "type": "PLUGINS:dm-core-plugins/common/Template", "path": "~._templates_[0]", "label": "Template1" }, { - "type": "PLUGINS:dm-core-plugins/table/Template", + "type": "PLUGINS:dm-core-plugins/common/Template", "path": "~._templates_[1]", "label": "Template2" }, { - "type": "PLUGINS:dm-core-plugins/table/Template", + "type": "PLUGINS:dm-core-plugins/common/Template", "path": "~._templates_[2]", "label": "Template3" } @@ -154,7 +154,7 @@ "type": "PLUGINS:dm-core-plugins/table/TablePluginConfig", "templates": [ { - "type": "PLUGINS:dm-core-plugins/table/Template", + "type": "PLUGINS:dm-core-plugins/common/Template", "path": "~._templates_[0]", "label": "Template1" } @@ -219,7 +219,7 @@ "type": "PLUGINS:dm-core-plugins/table/TablePluginConfig", "templates": [ { - "type": "PLUGINS:dm-core-plugins/table/Template", + "type": "PLUGINS:dm-core-plugins/common/Template", "path": "~._templates_[0]", "label": "Template1" } diff --git a/example/app/data_sources/DemoDataSource.json b/example/app/data_sources/DemoDataSource.json index 2e011971e..e62e76e25 100644 --- a/example/app/data_sources/DemoDataSource.json +++ b/example/app/data_sources/DemoDataSource.json @@ -5,7 +5,7 @@ "type": "mongo-db", "host": "db", "port": 27017, - "username": "root", + "username": "maf", "password": "xd7wCEhEx4kszsecYFfC", "tls": false, "database": "DemoDataSource", diff --git a/example/app/data_sources/ExtraDataSource.json b/example/app/data_sources/ExtraDataSource.json index 53f926f5b..6f45af72a 100644 --- a/example/app/data_sources/ExtraDataSource.json +++ b/example/app/data_sources/ExtraDataSource.json @@ -5,7 +5,7 @@ "type": "mongo-db", "host": "db", "port": 27017, - "username": "root", + "username": "maf", "password": "xd7wCEhEx4kszsecYFfC", "tls": false, "database": "ExtraDataSource", diff --git a/example/docker-compose.yaml b/example/docker-compose.yaml index ff0acdbff..bd1186b14 100644 --- a/example/docker-compose.yaml +++ b/example/docker-compose.yaml @@ -2,19 +2,18 @@ version: '3.4' services: dmss: - image: datamodelingtool.azurecr.io/dmss:v1.15.3 + image: datamodelingtool.azurecr.io/dmss:v1.16.2 platform: linux/amd64 restart: unless-stopped environment: AUTH_ENABLED: 0 ENVIRONMENT: local - # RESET_DATA_SOURCE: off - MONGO_INITDB_ROOT_USERNAME: root - MONGO_INITDB_ROOT_PASSWORD: xd7wCEhEx4kszsecYFfC +# RESET_DATA_SOURCE: off + MONGO_USERNAME: maf + MONGO_PASSWORD: xd7wCEhEx4kszsecYFfC SECRET_KEY: sg9aeUM5i1JO4gNN8fQadokJa3_gXQMLBjSGGYcfscs= # Don't reuse this in production... - DATA_SOURCE_FILES: '{ "name": "system", "repositories": { "db": { "type": "mongo-db", "host": "db", "port": 27017, "username": "root", "password": "xd7wCEhEx4kszsecYFfC", "tls": false, "database": "DMSS-core", "collection": "DMSS-core" } }}' - # volumes: - # - ../../data-modelling-storage-service/src:/code/src +# volumes: +# - ../../data-modelling-storage-service/src:/code/src ports: - '5000:5000' depends_on: @@ -22,15 +21,14 @@ services: db: image: mongo:3.4 - command: mongod --quiet - volumes: - - ./dmss-data/db:/data/db environment: - MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_USERNAME: maf MONGO_INITDB_ROOT_PASSWORD: xd7wCEhEx4kszsecYFfC + volumes: + - ./dmss-data/db:/data/db job-api: - image: datamodelingtool.azurecr.io/dm-job:v1.4.2 + image: datamodelingtool.azurecr.io/dm-job:v1.5.1 platform: linux/amd64 restart: unless-stopped environment: @@ -46,13 +44,15 @@ services: AZURE_SP_SECRET: ${AZURE_SP_SECRET} AZURE_JOB_TENANT_ID: 3aa4a235-b6e2-48d5-9195-7fcf05b459b0 AZURE_JOB_CLIENT_ID: 97a6b5bd-63fb-42c6-bb75-7e5de2394ba0 - DATA_SOURCE_FILES: '{ "name": "WorkflowDS", "repositories": { "db": { "type": "mongo-db", "host": "db", "port": 27017, "username": "root", "password": "xd7wCEhEx4kszsecYFfC", "tls": false, "database": "workflowDS", "collection": "default", "data_types": [] } }}' + MONGO_PASSWORD: xd7wCEhEx4kszsecYFfC #SIMA_LICENSE: | depends_on: - job-store - db volumes: +# - /var/run/docker.sock:/var/run/docker.sock # Needed for docker-in-docker jobs - ./job_handlers:/code/src/job_handler_plugins +# - ../../dm-job/src/job_handler_plugins:/code/src/job_handler_plugins ports: - '5001:5000' diff --git a/packages/dm-core-plugins/blueprints/table/Template.json b/packages/dm-core-plugins/blueprints/common/Template.json similarity index 100% rename from packages/dm-core-plugins/blueprints/table/Template.json rename to packages/dm-core-plugins/blueprints/common/Template.json diff --git a/packages/dm-core-plugins/blueprints/common/package.json b/packages/dm-core-plugins/blueprints/common/package.json new file mode 100644 index 000000000..e51fe914e --- /dev/null +++ b/packages/dm-core-plugins/blueprints/common/package.json @@ -0,0 +1,25 @@ +{ + "name": "common", + "type": "CORE:Package", + "isRoot": false, + "_meta_": { + "type": "CORE:Meta", + "version": "0.0.1", + "dependencies": [ + { + "type": "CORE:Dependency", + "alias": "CORE", + "address": "system/SIMOS", + "version": "0.0.1", + "protocol": "dmss" + }, + { + "type": "CORE:Dependency", + "alias": "PLUGINS", + "address": "system/Plugins", + "version": "0.0.1", + "protocol": "dmss" + } + ] + } +} diff --git a/packages/dm-core-plugins/blueprints/job/ControlConfig.json b/packages/dm-core-plugins/blueprints/job/ControlConfig.json new file mode 100644 index 000000000..6e696b564 --- /dev/null +++ b/packages/dm-core-plugins/blueprints/job/ControlConfig.json @@ -0,0 +1,25 @@ +{ + "name": "ControlConfig", + "type": "CORE:Blueprint", + "description": "", + "attributes": [ + { + "name": "type", + "type": "CORE:BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "recurring", + "type": "CORE:BlueprintAttribute", + "attributeType": "boolean", + "optional": true + }, + { + "name": "runnerTemplates", + "type": "CORE:BlueprintAttribute", + "attributeType": "PLUGINS:dm-core-plugins/common/Template", + "optional": true, + "dimensions": "*" + } + ] +} diff --git a/packages/dm-core-plugins/blueprints/list/ListPluginConfig.json b/packages/dm-core-plugins/blueprints/list/ListPluginConfig.json index 6dfb556e1..e2fce2169 100644 --- a/packages/dm-core-plugins/blueprints/list/ListPluginConfig.json +++ b/packages/dm-core-plugins/blueprints/list/ListPluginConfig.json @@ -79,7 +79,7 @@ "name": "templates", "type": "CORE:BlueprintAttribute", "description": "Attribute on parent (of same type as list) which should be used as template when instantiating new items. (e.g. 'template' will use the 'template' attribute on the parent.)", - "attributeType": "PLUGINS:dm-core-plugins/table/Template", + "attributeType": "PLUGINS:dm-core-plugins/common/Template", "optional": true, "dimensions": "*" }, diff --git a/packages/dm-core-plugins/blueprints/table/TablePluginConfig.json b/packages/dm-core-plugins/blueprints/table/TablePluginConfig.json index a48f9d643..4eb02dce6 100644 --- a/packages/dm-core-plugins/blueprints/table/TablePluginConfig.json +++ b/packages/dm-core-plugins/blueprints/table/TablePluginConfig.json @@ -51,7 +51,7 @@ "name": "templates", "type": "CORE:BlueprintAttribute", "description": "Attribute on parent (of same type as list) which should be used as template when instantiating new items. (e.g. 'template' will use the 'template' attribute on the parent.)", - "attributeType": "PLUGINS:dm-core-plugins/table/Template", + "attributeType": "PLUGINS:dm-core-plugins/common/Template", "optional": true, "dimensions": "*" }, diff --git a/packages/dm-core-plugins/src/job/CronJob.tsx b/packages/dm-core-plugins/src/job/CronJob.tsx index b7867db9b..b19495efb 100644 --- a/packages/dm-core-plugins/src/job/CronJob.tsx +++ b/packages/dm-core-plugins/src/job/CronJob.tsx @@ -111,6 +111,7 @@ export function ConfigureSchedule(props: { {showAdvanced ? ( { const chosenIntervalType = Object.entries(EInterval) @@ -143,6 +145,7 @@ export function ConfigureSchedule(props: { /> {interval !== EInterval.HOURLY && ( value )} @@ -157,6 +160,7 @@ export function ConfigureSchedule(props: { )} {interval === EInterval.HOURLY && ( i + 1)} initialSelectedOptions={[Number(hourStep)]} label={'Hour step'} diff --git a/packages/dm-core-plugins/src/job/JobControl.tsx b/packages/dm-core-plugins/src/job/JobControl.tsx index ac05c4d1a..4f4c18c16 100644 --- a/packages/dm-core-plugins/src/job/JobControl.tsx +++ b/packages/dm-core-plugins/src/job/JobControl.tsx @@ -1,17 +1,23 @@ import { DeleteJobResponse, EBlueprint, + ErrorResponse, IUIPlugin, JobStatus, Loading, + TemplateMenu, TJob, + TJobHandler, TRecurringJob, TSchedule, + TTemplate, + useDMSS, useDocument, useJob, } from '@development-framework/dm-core' import React, { useEffect, useState } from 'react' -import { Chip } from '@equinor/eds-core-react' +import { Button, Chip, Icon, Tooltip } from '@equinor/eds-core-react' +import { gear } from '@equinor/eds-icons' import styled from 'styled-components' import { scheduleTemplate } from './templateEntities' import { ConfigureRecurring, getVariant, JobLog, Progress } from './common' @@ -21,6 +27,9 @@ import { RerunButton, StartButton, } from './SimpleJobControlButtons' +import { toast } from 'react-toastify' +import { AxiosError } from 'axios' +import _ from 'lodash' const JobButtonWrapper = styled.div` margin-top: 0.5rem; @@ -34,23 +43,26 @@ const getControlButton = ( status: JobStatus, remove: () => Promise, start: () => void, - asCronJob: boolean = false + asCronJob: boolean = false, + isLoading: boolean = false ) => { + if (isLoading) return switch (status) { - case JobStatus.Unknown: - return ( - - ) case JobStatus.Completed: return ( ) case JobStatus.Failed: return + case JobStatus.Unknown: case JobStatus.Running: case JobStatus.Starting: case JobStatus.Registered: return + case JobStatus.NotStarted: + return ( + + ) default: return ( @@ -58,51 +70,128 @@ const getControlButton = ( } } +type TJobControlConfig = { + recurring?: boolean + hideLogs?: boolean + runnerTemplates?: TTemplate[] +} + +const defaultConfig: TJobControlConfig = { + recurring: undefined, + hideLogs: false, +} + export const JobControl = (props: IUIPlugin) => { - const { idReference } = props + const { idReference, config } = props + const dmssAPI = useDMSS() + + const internalConfig: TJobControlConfig = { ...defaultConfig, ...config } const { document: jobEntity, isLoading, + updateDocument, error: jobEntityError, } = useDocument(idReference, 0, false) const [asCronJob, setAsCronJob] = useState(false) const [schedule, setSchedule] = useState(scheduleTemplate()) + const [isTemplateMenuOpen, setTemplateMenuIsOpen] = useState(false) + const [templates, setTemplates] = useState([]) - const { start, error, logs, progress, status, remove } = useJob( - idReference, - jobEntity?.uid - ) + const { + start, + error, + logs, + progress, + status, + remove, + isLoading: jobIsLoading, + } = useJob(idReference, jobEntity?.uid) useEffect(() => { if (!jobEntity) return if (asCronJob || jobEntity.type === EBlueprint.RECURRING_JOB) setSchedule((jobEntity as TRecurringJob)?.schedule) if (jobEntity.type === EBlueprint.RECURRING_JOB) setAsCronJob(true) - }, [isLoading, jobEntityError, jobEntity]) - if (isLoading) return + Promise.all( + // @ts-ignore + internalConfig.runnerTemplates?.map(async (template: TTemplate) => { + const response = await dmssAPI.documentGet({ address: template.path }) + return response.data as TJobHandler + }) + // @ts-ignore + ).then((templates: TJobHandler[]) => setTemplates(templates)) + }, [jobEntity]) + + if (isLoading || !jobEntity) return if (error || jobEntityError) throw new Error(JSON.stringify(error || jobEntityError, null, 2)) return ( -
- + <> + {internalConfig.recurring !== true && ( + + )} - {getControlButton(status, remove, start, false)} - + {getControlButton(status, remove, start, false, jobIsLoading)} + {!internalConfig.hideLogs && } {status ?? 'Not registered'} + {internalConfig.runnerTemplates?.length && ( +
+ + + + { + dmssAPI + .documentGet({ address: template.path }) + .then((response) => { + const templateEntity: TJobHandler = + response.data as TJobHandler + updateDocument( + { ...jobEntity, runner: templateEntity }, + false + ).catch((error: AxiosError) => { + console.error(error) + toast.error(error.response?.data.message) + }) + }) + .catch((error: AxiosError) => { + console.error(error) + toast.error(error.response?.data.message) + }) + }} + onClose={() => setTemplateMenuIsOpen(false)} + isOpen={isTemplateMenuOpen} + title='Runner' + selected={templates.findIndex((template: TJobHandler) => + _.isEqual(template, jobEntity.runner) + )} + /> +
+ )}
{status === JobStatus.Running && progress !== null && ( )} -
+ ) } diff --git a/packages/dm-core/src/components/TemplateMenu.tsx b/packages/dm-core/src/components/TemplateMenu.tsx index 9eed347a1..8120354ac 100644 --- a/packages/dm-core/src/components/TemplateMenu.tsx +++ b/packages/dm-core/src/components/TemplateMenu.tsx @@ -1,5 +1,5 @@ -import { EdsProvider, Menu } from '@equinor/eds-core-react' -import React, { ReactNode, useRef, useState } from 'react' +import { Menu } from '@equinor/eds-core-react' +import React, { useRef } from 'react' export type TTemplate = { label: string @@ -10,12 +10,15 @@ interface TemplateMenuProps { templates: TTemplate[] onSelect: (template: TTemplate) => void isOpen: boolean + title?: string + selected?: number onClose: () => void anchorRef?: any } export const TemplateMenu = (props: TemplateMenuProps) => { - const { templates, onSelect, isOpen, onClose, anchorRef } = props + const { templates, onSelect, isOpen, onClose, anchorRef, title, selected } = + props const anchorEl = useRef(null) return ( @@ -27,16 +30,19 @@ export const TemplateMenu = (props: TemplateMenuProps) => { onClose={onClose} anchorEl={anchorRef ? anchorRef.current : anchorEl.current} > - {templates.map((template: TTemplate, index: number) => { - return ( - onSelect(template)} - > - {template.label} - - ) - })} + + {templates.map((template: TTemplate, index: number) => { + return ( + onSelect(template)} + active={selected === index} + > + {template.label} + + ) + })} + )