diff --git a/frontend/src/components/right/DataProcessing.vue b/frontend/src/components/right/DataProcessing.vue index 9aa554b..7f1c9d2 100644 --- a/frontend/src/components/right/DataProcessing.vue +++ b/frontend/src/components/right/DataProcessing.vue @@ -203,10 +203,10 @@ export default defineComponent({ "operator": "COUNT", "label": "カウント" }, - { - "operator": "FIRST", - "label": "最初の値" - } + // { + // "operator": "FIRST", + // "label": "最初の値" + // } ]); watchEffect(() => { diff --git a/plugin/kintone-addins/src/actions/data-processing.ts b/plugin/kintone-addins/src/actions/data-processing.ts new file mode 100644 index 0000000..310518f --- /dev/null +++ b/plugin/kintone-addins/src/actions/data-processing.ts @@ -0,0 +1,274 @@ +import { + IAction, + IActionResult, + IActionNode, + IActionProperty, + IContext, +} from "../types/ActionTypes"; +import { actionAddins } from "."; + +type DataProcessingProps = { + app: { + id: string; + name: string; + }; + conditionsQuery: string; + propcessing: { + varRootName: string; + fields: Field[]; + }; +}; + +type Field = { + name: string; + code: string; + type: string; + varName: string; + operator: string; +}; + +export class DataProcessingAction implements IAction { + name: string; + actionProps: IActionProperty[]; + dataProcessingProps: DataProcessingProps | null; + constructor() { + this.name = "データ処理"; + this.actionProps = []; + this.dataProcessingProps = null; + this.register(); + } + + async process( + nodes: IActionNode, + event: any, + context: IContext + ): Promise { + this.initActionProps(nodes); + this.initTypedActionProps(); + let result = { + canNext: true, + result: "", + } as IActionResult; + try { + if (!this.dataProcessingProps) { + return result; + } + + const data = await selectData(this.dataProcessingProps.conditionsQuery); + console.log("data ", data); + + context.variables[this.dataProcessingProps.propcessing.varRootName] = + this.dataProcessingProps.propcessing.fields.reduce((acc, f) => { + const v = calc(f, data); + if (v) { + acc[f.varName] = calc(f, data); + } + return acc; + }, {} as Var); + + console.log("context ", context); + return result; + } catch (e) { + console.error(e); + return result; + } + } + + register(): void { + actionAddins[this.name] = this; + } + + private initActionProps(nodes: IActionNode) { + this.actionProps = nodes.actionProps; + } + + private initTypedActionProps() { + this.dataProcessingProps = { + app: { + id: "", + name: "", + }, + conditionsQuery: "", + propcessing: { + varRootName: "", + fields: [], + }, + }; + for (const action of this.actionProps) { + if (action.component === "AppFieldSelect") { + this.dataProcessingProps.app.id = action.props.modelValue.app.id; + this.dataProcessingProps.app.name = action.props.modelValue.app.name; + } else if (action.component === "DataProcessing") { + this.dataProcessingProps.propcessing.varRootName = + action.props.modelValue.name; + for (const f of action.props.modelValue.vars) { + this.dataProcessingProps.propcessing.fields.push({ + name: f.field.name, + code: f.field.code, + type: f.field.type, + varName: f.vName, + operator: f.logicalOperator.operator, + }); + } + } else if (action.component === "ConditionInput") { + this.dataProcessingProps.conditionsQuery = JSON.parse( + action.props.modelValue + ).queryString; + } + } + } +} + +new DataProcessingAction(); + +const selectData = async (query?: string) => { + return kintone + .api(kintone.api.url("/k/v1/records", true), "GET", { + app: kintone.app.getId(), + query: query, + }) + .then((resp: Resp) => { + const result: Result = {}; + resp.records.forEach((element) => { + for (const [key, value] of Object.entries(element)) { + if (!result[key]) { + result[key] = { type: value.type, value: [] }; + } + result[key].value.push(value.value); + } + }); + return result; + }); +}; + +type Resp = { records: RespRecordType[] }; + +type RespRecordType = { + [key: string]: { + type: string; + value: any; + }; +}; + +type Result = { + [key: string]: { + type: string; + value: any[]; + }; +}; + +type Var = { + [key: string]: any; +}; + +const ERROR_TYPE = "ERROR_TYPE"; + +const calc = (field: Field, result: Result) => { + const type = typeCheck(field.type); + if (!type) { + return ERROR_TYPE; + } + + const fun = + calcFunc[`${type}_${Operator[field.operator as keyof typeof Operator]}`]; + if (!fun) { + return ERROR_TYPE; + } + const values = result[field.code].value; + if (!values) { + return null; + } + return fun(values); +}; + +const typeCheck = (type: string) => { + switch (type) { + case "RECORD_NUMBER": + case "NUMBER": + return CalcType.NUMBER; + case "SINGLE_LINE_TEXT": + case "MULTI_LINE_TEXT": + case "RICH_TEXT": + return CalcType.STRING; + case "UPDATED_TIME": + case "DATE": + case "TIME": + case "DATETIME": + return CalcType.DATE; + default: + return null; + } +}; + +enum Operator { + SUM = "SUM", + AVG = "AVG", + MAX = "MAX", + MIN = "MIN", + COUNT = "COUNT", +} + +enum CalcType { + NUMBER = "number", + STRING = "string", + DATE = "date", + TIME = "time", + DATETIME = "datetime", +} + +const calcFunc: Record string | null> = { + [`${CalcType.NUMBER}_${Operator.COUNT}`]: (value: string[]) => + value.length.toString(), + [`${CalcType.STRING}_${Operator.COUNT}`]: (value: string[]) => + value.length.toString(), + [`${CalcType.DATE}_${Operator.COUNT}`]: (value: string[]) => + value.length.toString(), + [`${CalcType.TIME}_${Operator.COUNT}`]: (value: string[]) => + value.length.toString(), + [`${CalcType.DATETIME}_${Operator.COUNT}`]: (value: string[]) => + value.length.toString(), + + [`${CalcType.NUMBER}_${Operator.SUM}`]: (value: string[]) => + value.reduce((acc, v) => acc + Number(v), 0).toString(), + [`${CalcType.NUMBER}_${Operator.AVG}`]: (value: string[]) => + (value.reduce((acc, v) => acc + Number(v), 0) / value.length).toString(), + [`${CalcType.NUMBER}_${Operator.MAX}`]: (value: string[]) => + Math.max(...value.map(Number)).toString(), + [`${CalcType.NUMBER}_${Operator.MIN}`]: (value: string[]) => + Math.min(...value.map(Number)).toString(), + + [`${CalcType.STRING}_${Operator.SUM}`]: (value: string[]) => value.join(" "), + + [`${CalcType.DATE}_${Operator.MAX}`]: (value: string[]) => + value.reduce((maxDate, currentDate) => + maxDate > currentDate ? maxDate : currentDate + ), + + [`${CalcType.DATE}_${Operator.MIN}`]: (value: string[]) => + value.reduce((minDate, currentDate) => + minDate < currentDate ? minDate : currentDate + ), + + [`${CalcType.TIME}_${Operator.MAX}`]: (value: string[]) => + value.reduce((maxTime, currentTime) => + maxTime > currentTime ? maxTime : currentTime + ), + [`${CalcType.TIME}_${Operator.MIN}`]: (value: string[]) => + value.reduce((minTime, currentTime) => + minTime < currentTime ? minTime : currentTime + ), + + [`${CalcType.DATETIME}_${Operator.MAX}`]: (value: string[]) => + value.reduce((maxDateTime, currentDateTime) => + new Date(maxDateTime) > new Date(currentDateTime) + ? maxDateTime + : currentDateTime + ), + + [`${CalcType.DATETIME}_${Operator.MIN}`]: (value: string[]) => + value.reduce((minDateTime, currentDateTime) => + new Date(minDateTime) < new Date(currentDateTime) + ? minDateTime + : currentDateTime + ), +}; diff --git a/plugin/kintone-addins/src/types/action-process.ts b/plugin/kintone-addins/src/types/action-process.ts index 1f1889d..2bf094c 100644 --- a/plugin/kintone-addins/src/types/action-process.ts +++ b/plugin/kintone-addins/src/types/action-process.ts @@ -6,6 +6,7 @@ import '../actions/field-shown'; import '../actions/error-show'; import '../actions/button-add'; import '../actions/condition-action'; +import '../actions/data-processing'; import { ActionFlow,IActionFlow, IActionResult,IContext } from "./ActionTypes"; export class ActionProcess{