From af86edd3e2074e43e9d1bf50dbd350dc267d8c48 Mon Sep 17 00:00:00 2001 From: Mouriya Date: Thu, 11 Jul 2024 10:59:36 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E6=90=8D=E5=82=B7=E3=81=97=E3=81=9F?= =?UTF-8?q?=E9=83=A8=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/right/DataMapping.vue | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/right/DataMapping.vue b/frontend/src/components/right/DataMapping.vue index 3930255..bbe937b 100644 --- a/frontend/src/components/right/DataMapping.vue +++ b/frontend/src/components/right/DataMapping.vue @@ -18,28 +18,29 @@ -
+
-
+
ソース
-
+
目標
- - +
+ キー +
- +
-
+
-
+
+
+ + +
+ +
+ +
@@ -100,12 +107,19 @@ type Props = { } } }; + +type Value = { + data: ValueType[]; + createWithNull: boolean; +} + type ValueType = { id: string; from: object; to: typeof IAppFields & { isDialogVisible: boolean; }; + isKey: boolean; } @@ -132,7 +146,7 @@ export default defineComponent({ default: '', }, modelValue: { - type: Object as () => ValueType[], + type: Object as () => Value, }, placeholder: { type: String, @@ -161,7 +175,9 @@ export default defineComponent({ ); } - const mappingProps = computed(() => props.modelValue ?? []); + const mappingProps = computed(() => props.modelValue?.data ?? []); + + const createWithNull = computed(() => props.modelValue?.createWithNull) watch(() => sourceAppId.value, async (newId, oldId) => { if (!newId) return; @@ -180,23 +196,28 @@ export default defineComponent({ app: sourceApp.value, fields: [f], isDialogVisible: false - } + }, + isKey: true } }) }) - const modelValue = props.modelValue ?? []; + const modelValueData = props.modelValue?.data ?? []; - if (modelValue.length === 0 || newId !== oldId) { - emit('update:modelValue', a); + const createWithNull = props.modelValue?.createWithNull ?? false + + if (modelValueData.length === 0 || newId !== oldId) { + // emit('update:modelValue', a); + emit('update:modelValue', { data: a, createWithNull: createWithNull }); return; } - const modelValueFieldNames = modelValue.map(item => item.to.fields[0].name); + const modelValueFieldNames = modelValueData.map(item => item.to.fields[0].name); const newFields = a.filter(field => !modelValueFieldNames.includes(field.to.fields[0].name)); - const updatedModelValue = [...modelValue, ...newFields]; + const updatedModelValue = [...modelValueData, ...newFields]; - emit('update:modelValue', updatedModelValue); + emit('update:modelValue', { data: updatedModelValue, createWithNull: createWithNull }); + // emit('update:modelValue', updatedModelValue); }) console.log(mappingProps.value); @@ -231,6 +252,7 @@ export default defineComponent({ toDgIsShow: ref(false), closeToDg, mappingProps, + createWithNull, // addMappingObject: () => mappingProps.push(defaultMappingProp()), // deleteMappingObject, mappingObjectsInputDisplay, @@ -240,7 +262,7 @@ export default defineComponent({ config: { canInput: false, buttonsConfig: [ - { label: '変数', color: 'green', type: 'VariableAdd',editable:false }, + { label: '変数', color: 'green', type: 'VariableAdd', editable: false }, ] } }; From 24a70aed2e71830a82c9286cf971049aa20152d9 Mon Sep 17 00:00:00 2001 From: Mouriya Date: Thu, 11 Jul 2024 18:57:30 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E3=80=8CDataMapping=E3=80=8D=E3=82=B3?= =?UTF-8?q?=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=A8?= =?UTF-8?q?=E3=83=97=E3=83=A9=E3=82=B0=E3=82=A4=E3=83=B3=20=E6=96=B0?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=80=8C=E6=9B=B4=E6=96=B0=E5=AF=BE=E8=B1=A1?= =?UTF-8?q?=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/right/DataMapping.vue | 43 ++- .../src/actions/data-mapping.ts | 244 +++++++++++++----- 2 files changed, 194 insertions(+), 93 deletions(-) diff --git a/frontend/src/components/right/DataMapping.vue b/frontend/src/components/right/DataMapping.vue index bbe937b..4ab715b 100644 --- a/frontend/src/components/right/DataMapping.vue +++ b/frontend/src/components/right/DataMapping.vue @@ -96,7 +96,7 @@ import FieldSelect from '../FieldSelect.vue'; import IAppFields from './AppFieldSelect.vue'; import { api } from 'boot/axios'; -type Props = { +type ContextProps = { props?: { name: string; modelValue?: { @@ -108,12 +108,12 @@ type Props = { } }; -type Value = { - data: ValueType[]; +type CurrentModelValueType = { + data: MappingValueType[]; createWithNull: boolean; } -type ValueType = { +type MappingValueType = { id: string; from: object; to: typeof IAppFields & { @@ -122,6 +122,7 @@ type ValueType = { isKey: boolean; } +const blackListLabelName = ['レコード番号','作業者','更新者','更新日時','作成日時','作成者'] export default defineComponent({ name: 'DataMapping', @@ -134,7 +135,7 @@ export default defineComponent({ }, props: { context: { - type: Array, + type: Array, default: '', }, displayName: { @@ -146,7 +147,7 @@ export default defineComponent({ default: '', }, modelValue: { - type: Object as () => Value, + type: Object as () => CurrentModelValueType, }, placeholder: { type: String, @@ -162,31 +163,30 @@ export default defineComponent({ const source = props.context.find(element => element?.props?.name === 'sources') const sourceApp = computed(() => source?.props?.modelValue?.app); - + const sourceAppId = computed(() => sourceApp.value?.id); const closeDg = () => { - emit('update:modelValue', mappingProps.value - ); + emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value }); } const closeToDg = () => { - emit('update:modelValue', mappingProps.value - ); + emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value }); } const mappingProps = computed(() => props.modelValue?.data ?? []); - const createWithNull = computed(() => props.modelValue?.createWithNull) + const createWithNull = ref(props.modelValue?.createWithNull ?? false) watch(() => sourceAppId.value, async (newId, oldId) => { if (!newId) return; - const a = await api.get('api/v1/appfields', { + const ktAppFields = await api.get('api/v1/appfields', { params: { app: newId } }).then(res => { return Object.values(res.data.properties) + .filter(f => !blackListLabelName.find(label => f.label === label)) .map(f => ({ name: f.label, objectType: 'field', ...f })) .map(f => { return { @@ -197,7 +197,7 @@ export default defineComponent({ fields: [f], isDialogVisible: false }, - isKey: true + isKey: false } }) }) @@ -206,21 +206,18 @@ export default defineComponent({ const createWithNull = props.modelValue?.createWithNull ?? false if (modelValueData.length === 0 || newId !== oldId) { - // emit('update:modelValue', a); - emit('update:modelValue', { data: a, createWithNull: createWithNull }); + emit('update:modelValue', { data: ktAppFields, createWithNull: createWithNull }); return; } const modelValueFieldNames = modelValueData.map(item => item.to.fields[0].name); - const newFields = a.filter(field => !modelValueFieldNames.includes(field.to.fields[0].name)); + const newFields = ktAppFields.filter(field => !modelValueFieldNames.includes(field.to.fields[0].name)); - const updatedModelValue = [...modelValueData, ...newFields]; - - emit('update:modelValue', { data: updatedModelValue, createWithNull: createWithNull }); - // emit('update:modelValue', updatedModelValue); + const updatedModelValueData = [...modelValueData, ...newFields]; + emit('update:modelValue', { data: updatedModelValueData, createWithNull: createWithNull }); }) - console.log(mappingProps.value); + console.log(createWithNull.value); // const deleteMappingObject = (index: number) => mappingProps.length === 1 // ? mappingProps.splice(0, mappingProps.length, defaultMappingProp()) @@ -243,7 +240,7 @@ export default defineComponent({ //集計処理方法 watchEffect(() => { - emit('update:modelValue', mappingProps.value); + emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value }); }); return { uuidv4, diff --git a/plugin/kintone-addins/src/actions/data-mapping.ts b/plugin/kintone-addins/src/actions/data-mapping.ts index ec08137..73bc119 100644 --- a/plugin/kintone-addins/src/actions/data-mapping.ts +++ b/plugin/kintone-addins/src/actions/data-mapping.ts @@ -6,54 +6,57 @@ import { IContext, } from "../types/ActionTypes"; import { actionAddins } from "."; +import { KintoneRestAPIClient } from "@kintone/rest-api-client"; interface Props { displayName: string; sources: Sources; - dataMapping: DataMapping[]; + dataMapping: DataMapping; } interface DataMapping { + data: Mapping[]; + createWithNull: boolean; +} + +interface Mapping { id: string; from: From; to: To; + isKey: boolean; } interface To { app: App; - fields: Field[]; + fields: { + name: string; + type: string; + code: string; + label: string; + noLabel: boolean; + required: boolean; + minLength: string; + maxLength: string; + expression: string; + hideExpression: boolean; + unique: boolean; + defaultValue: string; + }[]; isDialogVisible: boolean; } -interface Field { - name: string; - type: string; - code: string; - label: string; - noLabel: boolean; - required: boolean; - minLength: string; - maxLength: string; - expression: string; - hideExpression: boolean; - unique: boolean; - defaultValue: string; -} - interface From { sharedText: string; _t: string; id: string; objectType: string; - name: Name; + name: { + name: string; + }; actionName: string; displayName: string; } -interface Name { - name: string; -} - interface Sources { app: App; } @@ -84,32 +87,37 @@ export class DataMappingAction implements IAction { this.actionProps = prop.actionProps; this.dataMappingProps = prop.ActionValue as Props; console.log(prop.ActionValue); - - // this.initTypedActionProps(); let result = { canNext: true, result: "", } as IActionResult; try { - const record = this.dataMappingProps.dataMapping - .filter( - (item) => - item.from.objectType === "variable" && - item.from.name.name && - item.to.app && - item.to.fields && - item.to.fields.length > 0 - ) - .reduce((accumulator, item) => { - return {...accumulator, [item.to.fields[0].code]: { - value: getValueByPath(context.variables, item.from.name.name), - }}; - }, {}); - if (record && Object.keys(record).length > 0) { - await kintone.api(kintone.api.url("/k/v1/record.json", true), "POST", { - app: this.dataMappingProps.sources.app.id, - record: record, - }); + // createWithNull が有効な場合は、4 番目のパラメーターを true にして doUpdate 関数ブランチを実行します。 + if (this.dataMappingProps.dataMapping.createWithNull === true) { + await doUpdate( + this.dataMappingProps.dataMapping.data, + this.dataMappingProps.sources.app.id, + context, + true // キーがない場合、またはキーでターゲットが見つからない場合に、マッピング条件によって新しいレコードを作成するかどうかを決定するために使用されます。 + ); + } else if ( + // キーがないと更新対象を取得できないため、この時点でのみ更新が行われます。 doUpdate 関数の 4 番目のパラメーターは false です。 + this.dataMappingProps.dataMapping.data + .map((m) => m.isKey) + .find((isKey) => isKey === true) + ) { + await doUpdate( + this.dataMappingProps.dataMapping.data, + this.dataMappingProps.sources.app.id, + context, + false + ); + } else { + await doCreate( + this.dataMappingProps.dataMapping.data, + this.dataMappingProps.sources.app.id, + context + ); } } catch (error) { console.error("DataMappingAction error", error); @@ -127,40 +135,136 @@ export class DataMappingAction implements IAction { new DataMappingAction(); -const getValueByPath = (obj: any, path: string) => { +const getContextVarByPath = (obj: any, path: string) => { return path.split(".").reduce((o, k) => (o || {})[k], obj); }; -type Resp = { records: RespRecordType[] }; - -type RespRecordType = { - [key: string]: { - type: string; - value: any; +interface UpdateRecord { + id: string; + record: { + [key: string]: { + value: any; + }; }; -}; +} -type Result = { - type: string; - value: any[]; -}; +const client = new KintoneRestAPIClient(); -const selectData = async (appid: string, field: string): Promise => { - return kintone - .api(kintone.api.url("/k/v1/records", true), "GET", { - app: appid ?? kintone.app.getId(), - fields: [field], - }) - .then((resp: Resp) => { - const result: Result = { type: "", value: [] }; - resp.records.forEach((element) => { - for (const [key, value] of Object.entries(element)) { - if (result.type === "") { - result.type = value.type; - } - result.value.push(value.value); +const doUpdate = async ( + mappingData: Mapping[], + appId: string, + context: any, + needCreate: boolean +) => { + const targetField = await findUpdateField(mappingData, appId, context); + console.log(targetField); + if (targetField.records.length === 0 && needCreate) { + await doCreate(mappingData, appId, context); + } else { + // マッピングデータを単純なオブジェクトに処理し、ソース値が変数の場合は変数を置き換えます。 + const mappingRules = mappingData + .filter((m) => Object.keys(m.from).length > 0) + .map((m) => { + if (m.from.objectType === "variable") { + return { + value: getContextVarByPath(context.variables, m.from.name.name), + code: m.to.fields[0].code, + }; + } else { + return { + value: m.from.sharedText, + code: m.to.fields[0].code, + }; } }); - return result; + + const updateRecords: UpdateRecord[] = targetField.records.map( + (targetRecord) => { + const updateRecord: UpdateRecord["record"] = {}; + + // マッピング内のルールにヒットしたフィールドのみが更新されます。 + for (const mapping of mappingRules) { + if (targetRecord[mapping.code]) { + updateRecord[mapping.code] = { + value: mapping.value, + }; + } + } + + return { + id: targetRecord.$id.value as string, + record: updateRecord, + }; + } + ); + + console.log(updateRecords); + + await client.record.updateRecords({ + app: appId, + records: updateRecords, }); + } +}; + +const findUpdateField = async ( + mappingData: Mapping[], + appId: string, + context: any +) => { + const queryStr = mappingData + .filter((m) => m.to.app && m.to.fields && m.to.fields.length > 0 && m.isKey) + .map((m) => { + if (m.from.objectType === "variable") { + return `${m.to.fields[0].code} = "${getContextVarByPath( + context.variables, + m.from.name.name + )}"`; + } else { + return `${m.to.fields[0].code}=${m.from.sharedText}`; + } + }) + .join("&"); + // 検索条件が空の場合は全レコードを返すため、検索対象が見つからない場合は検索は行われません。 + if (queryStr.length === 0) { + return { + records: [], + }; + } else { + return await client.record.getRecords({ + app: appId, + // query: undefined + query: queryStr, + }); + } +}; + +const doCreate = async ( + mappingData: Mapping[], + appId: string, + context: any +) => { + const record = mappingData + .filter( + (item) => + item.from.objectType === "variable" && + item.from.name.name && + item.to.app && + item.to.fields && + item.to.fields.length > 0 + ) + .reduce((accumulator, item) => { + return { + ...accumulator, + [item.to.fields[0].code]: { + value: getContextVarByPath(context.variables, item.from.name.name), + }, + }; + }, {}); + if (record && Object.keys(record).length > 0) { + await kintone.api(kintone.api.url("/k/v1/record.json", true), "POST", { + app: appId, + record: record, + }); + } }; From 4ac4c9e9f45b37b06a37281abdf357ab38da785d Mon Sep 17 00:00:00 2001 From: Mouriya Date: Thu, 11 Jul 2024 20:23:48 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=E3=80=8Ckintone=20lookup=E3=80=8D=E3=81=A8?= =?UTF-8?q?=E7=B5=84=E3=81=BF=E5=90=88=E3=82=8F=E3=81=9B=E3=82=8B=E3=81=A8?= =?UTF-8?q?=E3=83=AD=E3=83=83=E3=82=AF=E3=81=95=E3=82=8C=E3=81=9F=E3=83=95?= =?UTF-8?q?=E3=82=A3=E3=83=BC=E3=83=AB=E3=83=89=E3=82=92=E9=99=A4=E5=A4=96?= =?UTF-8?q?=E3=81=97=E3=81=BE=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConditionEditor/ConditionObject.vue | 12 ++- frontend/src/components/right/DataMapping.vue | 75 +++++++++++++------ .../src/actions/data-mapping.ts | 43 ++++++++--- 3 files changed, 97 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/ConditionEditor/ConditionObject.vue b/frontend/src/components/ConditionEditor/ConditionObject.vue index 77ea9cf..40ca757 100644 --- a/frontend/src/components/ConditionEditor/ConditionObject.vue +++ b/frontend/src/components/ConditionEditor/ConditionObject.vue @@ -1,6 +1,6 @@