import '@material/web/icon/icon.js'
import '@operato/data-grist/ox-grist.js'
import gql from 'graphql-tag'
import { css, html, LitElement } from 'lit'
import { customElement, property, query, state } from 'lit/decorators.js'
import { DataGrist } from '@operato/data-grist/ox-grist.js'
import { client } from '@operato/graphql'
import { HelpDecoratedRenderer } from '@operato/help/help-decorated-renderer.js'
import { i18next, localize } from '@operato/i18n'
import { isMobileDevice } from '@operato/utils'
import { CommonHeaderStyles } from '@operato/styles'
import { FetchOption } from '@operato/data-grist'
import { createEnvVarActionInjector, resolveEnvVars } from '../viewparts/env-var-action-injector.js'
const SelectFields = ['name', 'description', 'sequence', 'task', 'connection', 'params', 'result', 'skip', 'log']
@customElement('scenario-detail')
class ScenarioDetail extends localize(i18next)(LitElement) {
static styles = [
CommonHeaderStyles,
css`
:host {
display: flex;
flex-direction: column;
background-color: var(--md-sys-color-surface);
}
ox-grist {
flex: 1;
}
`
]
@property({ type: Object }) scenario: any
@property({ type: Object }) gristConfig: any
@property({ type: Object }) taskTypes: any
@query('ox-grist') grist!: DataGrist
render() {
return html`
`
}
async firstUpdated() {
this.gristConfig = {
list: { fields: ['name', 'description', 'task'] },
columns: [
{ type: 'gutter', gutterName: 'row-selector', multiple: true, fixed: true },
{
type: 'gutter',
gutterName: 'button',
fixed: true,
icon: 'add',
handlers: {
click: 'record-copy'
}
},
{ type: 'gutter', gutterName: 'sequence', fixed: true },
{
type: 'gutter',
gutterName: 'button',
fixed: true,
icon: 'arrow_upward',
handlers: {
click: 'move-up'
}
},
{
type: 'gutter',
gutterName: 'button',
fixed: true,
icon: 'arrow_downward',
handlers: {
click: 'move-down'
}
},
{
type: 'number',
name: 'sequence',
fixed: true,
hidden: true
},
{
type: 'string',
name: 'id',
hidden: true
},
{
type: 'string',
name: 'name',
fixed: true,
header: i18next.t('field.name'),
record: {
editable: true
},
width: 140
},
{
type: 'string',
name: 'description',
header: i18next.t('field.description'),
record: {
editable: true
},
width: 240
},
{
type: 'boolean',
name: 'result',
header: i18next.t('field.result'),
record: {
editable: true
},
width: 80
},
{
type: 'boolean',
name: 'skip',
header: i18next.t('field.skip'),
record: {
editable: true
},
width: 80
},
{
type: 'boolean',
name: 'log',
header: i18next.t('field.log'),
record: {
editable: true
},
width: 80
},
{
type: 'connection',
name: 'connection',
header: i18next.t('field.connection'),
record: {
editable: true
},
width: 160
},
{
type: 'task-type',
name: 'task',
header: i18next.t('field.task'),
record: {
renderer: HelpDecoratedRenderer,
editable: true,
help: value => this.taskTypes?.[value]?.help,
connectionName: async (value, column, record, row, field) => {
/* return connectionName */
return record.connection
}
},
width: 200
},
{
type: 'parameters',
name: 'params',
header: i18next.t('field.params'),
record: {
editable: true,
options: async (value, column, record, row, field) => {
// 등록된 task type 이 아닌 step.task 디스트럭처 보호 + 진단 경고
let taskEntry: any = null
if (record.task) {
taskEntry = this.taskTypes?.[record.task] || null
if (!taskEntry && this.taskTypes) {
console.warn(
`[scenario-detail] task type '${record.task}' (step '${record.name}') is not registered. ` +
`Server-side handler missing or not bootstrapped.`
)
}
}
var { name, parameterSpec: spec, help } = taskEntry || ({} as any)
const context = this.grist
const scenarioName = this.scenario?.name
const stepName = record?.name
const domainAttrParams = (Array.isArray(spec) ? spec : []).filter(
(p: any) => p?.useDomainAttribute && p?.name
)
const keys = scenarioName && stepName
? domainAttrParams.map((p: any) => `Step::${scenarioName}::${stepName}::${p.name}`)
: []
const resolutions = await resolveEnvVars(keys)
return {
name,
spec,
help,
context,
objectified: true,
actionInjector:
scenarioName && stepName
? createEnvVarActionInjector(
(propName: string) => `Step::${scenarioName}::${stepName}::${propName}`,
resolutions,
() => this.grist.fetch()
)
: undefined
}
},
renderer: 'json5'
},
width: 300
}
],
rows: {
selectable: {
multiple: true
}
},
pagination: {
infinite: true
},
sorters: [
{
name: 'sequence'
}
]
}
}
async fetchHandler({ page, limit, sorters = [] }: FetchOption) {
const response = await client.query({
query: gql`
query {
steps (
filters: [{
name: "scenarioId",
value: "${this.scenario.id}",
operator: "eq"
}],
sortings: { name: "sequence" }
) {
items {
id
${SelectFields.join('\n')}
}
total
}
}
`
})
return {
total: response.data.steps.total || 0,
records: response.data.steps.items || []
}
}
async fetchTaskType(name) {
const response = await client.query({
query: gql`
query ($name: String!) {
taskType(name: $name) {
name
description
help
parameterSpec {
type
name
label
placeholder
property
styles
}
}
}
`,
variables: {
name
}
})
return response.data.taskType
}
async _updateSteps() {
let patches = this.grist.dirtyData.records
if (patches) {
patches = patches.map(patch => {
const { __origin__: { __typename, ...patchField } = {}, __dirtyfields__ } = patch
for (let key in __dirtyfields__) {
patchField[key] = __dirtyfields__[key].after
}
return patchField
})
const response = await client.mutate({
mutation: gql`
mutation ($scenarioId: String!, $patches: [StepPatch!]!) {
updateMultipleStep(scenarioId: $scenarioId, patches: $patches) {
name
}
}
`,
variables: {
scenarioId: this.scenario.id,
patches
}
})
if (!response.errors) {
this.grist.fetch()
this.requestRefresh()
}
}
}
async _deleteSteps() {
this.grist.deleteSelectedRecords(true)
}
requestRefresh() {
this.dispatchEvent(new CustomEvent('requestRefresh'))
}
}