「DataMapping」コンポーネントとプラグイン 新機能「更新対象」

This commit is contained in:
Mouriya
2024-07-11 18:57:30 +09:00
parent af86edd3e2
commit 24a70aed2e
2 changed files with 194 additions and 93 deletions

View File

@@ -96,7 +96,7 @@ import FieldSelect from '../FieldSelect.vue';
import IAppFields from './AppFieldSelect.vue'; import IAppFields from './AppFieldSelect.vue';
import { api } from 'boot/axios'; import { api } from 'boot/axios';
type Props = { type ContextProps = {
props?: { props?: {
name: string; name: string;
modelValue?: { modelValue?: {
@@ -108,12 +108,12 @@ type Props = {
} }
}; };
type Value = { type CurrentModelValueType = {
data: ValueType[]; data: MappingValueType[];
createWithNull: boolean; createWithNull: boolean;
} }
type ValueType = { type MappingValueType = {
id: string; id: string;
from: object; from: object;
to: typeof IAppFields & { to: typeof IAppFields & {
@@ -122,6 +122,7 @@ type ValueType = {
isKey: boolean; isKey: boolean;
} }
const blackListLabelName = ['レコード番号','作業者','更新者','更新日時','作成日時','作成者']
export default defineComponent({ export default defineComponent({
name: 'DataMapping', name: 'DataMapping',
@@ -134,7 +135,7 @@ export default defineComponent({
}, },
props: { props: {
context: { context: {
type: Array<Props>, type: Array<ContextProps>,
default: '', default: '',
}, },
displayName: { displayName: {
@@ -146,7 +147,7 @@ export default defineComponent({
default: '', default: '',
}, },
modelValue: { modelValue: {
type: Object as () => Value, type: Object as () => CurrentModelValueType,
}, },
placeholder: { placeholder: {
type: String, type: String,
@@ -162,31 +163,30 @@ export default defineComponent({
const source = props.context.find(element => element?.props?.name === 'sources') const source = props.context.find(element => element?.props?.name === 'sources')
const sourceApp = computed(() => source?.props?.modelValue?.app); const sourceApp = computed(() => source?.props?.modelValue?.app);
const sourceAppId = computed(() => sourceApp.value?.id); const sourceAppId = computed(() => sourceApp.value?.id);
const closeDg = () => { const closeDg = () => {
emit('update:modelValue', mappingProps.value emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
);
} }
const closeToDg = () => { const closeToDg = () => {
emit('update:modelValue', mappingProps.value emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
);
} }
const mappingProps = computed(() => props.modelValue?.data ?? []); 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) => { watch(() => sourceAppId.value, async (newId, oldId) => {
if (!newId) return; if (!newId) return;
const a = await api.get('api/v1/appfields', { const ktAppFields = await api.get('api/v1/appfields', {
params: { params: {
app: newId app: newId
} }
}).then(res => { }).then(res => {
return Object.values(res.data.properties) return Object.values(res.data.properties)
.filter(f => !blackListLabelName.find(label => f.label === label))
.map(f => ({ name: f.label, objectType: 'field', ...f })) .map(f => ({ name: f.label, objectType: 'field', ...f }))
.map(f => { .map(f => {
return { return {
@@ -197,7 +197,7 @@ export default defineComponent({
fields: [f], fields: [f],
isDialogVisible: false isDialogVisible: false
}, },
isKey: true isKey: false
} }
}) })
}) })
@@ -206,21 +206,18 @@ export default defineComponent({
const createWithNull = props.modelValue?.createWithNull ?? false const createWithNull = props.modelValue?.createWithNull ?? false
if (modelValueData.length === 0 || newId !== oldId) { if (modelValueData.length === 0 || newId !== oldId) {
// emit('update:modelValue', a); emit('update:modelValue', { data: ktAppFields, createWithNull: createWithNull });
emit('update:modelValue', { data: a, createWithNull: createWithNull });
return; return;
} }
const modelValueFieldNames = modelValueData.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 newFields = ktAppFields.filter(field => !modelValueFieldNames.includes(field.to.fields[0].name));
const updatedModelValue = [...modelValueData, ...newFields]; const updatedModelValueData = [...modelValueData, ...newFields];
emit('update:modelValue', { data: updatedModelValueData, createWithNull: createWithNull });
emit('update:modelValue', { data: updatedModelValue, createWithNull: createWithNull });
// emit('update:modelValue', updatedModelValue);
}) })
console.log(mappingProps.value); console.log(createWithNull.value);
// const deleteMappingObject = (index: number) => mappingProps.length === 1 // const deleteMappingObject = (index: number) => mappingProps.length === 1
// ? mappingProps.splice(0, mappingProps.length, defaultMappingProp()) // ? mappingProps.splice(0, mappingProps.length, defaultMappingProp())
@@ -243,7 +240,7 @@ export default defineComponent({
//集計処理方法 //集計処理方法
watchEffect(() => { watchEffect(() => {
emit('update:modelValue', mappingProps.value); emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
}); });
return { return {
uuidv4, uuidv4,

View File

@@ -6,54 +6,57 @@ import {
IContext, IContext,
} from "../types/ActionTypes"; } from "../types/ActionTypes";
import { actionAddins } from "."; import { actionAddins } from ".";
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
interface Props { interface Props {
displayName: string; displayName: string;
sources: Sources; sources: Sources;
dataMapping: DataMapping[]; dataMapping: DataMapping;
} }
interface DataMapping { interface DataMapping {
data: Mapping[];
createWithNull: boolean;
}
interface Mapping {
id: string; id: string;
from: From; from: From;
to: To; to: To;
isKey: boolean;
} }
interface To { interface To {
app: App; 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; 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 { interface From {
sharedText: string; sharedText: string;
_t: string; _t: string;
id: string; id: string;
objectType: string; objectType: string;
name: Name; name: {
name: string;
};
actionName: string; actionName: string;
displayName: string; displayName: string;
} }
interface Name {
name: string;
}
interface Sources { interface Sources {
app: App; app: App;
} }
@@ -84,32 +87,37 @@ export class DataMappingAction implements IAction {
this.actionProps = prop.actionProps; this.actionProps = prop.actionProps;
this.dataMappingProps = prop.ActionValue as Props; this.dataMappingProps = prop.ActionValue as Props;
console.log(prop.ActionValue); console.log(prop.ActionValue);
// this.initTypedActionProps();
let result = { let result = {
canNext: true, canNext: true,
result: "", result: "",
} as IActionResult; } as IActionResult;
try { try {
const record = this.dataMappingProps.dataMapping // createWithNull が有効な場合は、4 番目のパラメーターを true にして doUpdate 関数ブランチを実行します。
.filter( if (this.dataMappingProps.dataMapping.createWithNull === true) {
(item) => await doUpdate(
item.from.objectType === "variable" && this.dataMappingProps.dataMapping.data,
item.from.name.name && this.dataMappingProps.sources.app.id,
item.to.app && context,
item.to.fields && true // キーがない場合、またはキーでターゲットが見つからない場合に、マッピング条件によって新しいレコードを作成するかどうかを決定するために使用されます。
item.to.fields.length > 0 );
) } else if (
.reduce((accumulator, item) => { // キーがないと更新対象を取得できないため、この時点でのみ更新が行われます。 doUpdate 関数の 4 番目のパラメーターは false です。
return {...accumulator, [item.to.fields[0].code]: { this.dataMappingProps.dataMapping.data
value: getValueByPath(context.variables, item.from.name.name), .map((m) => m.isKey)
}}; .find((isKey) => isKey === true)
}, {}); ) {
if (record && Object.keys(record).length > 0) { await doUpdate(
await kintone.api(kintone.api.url("/k/v1/record.json", true), "POST", { this.dataMappingProps.dataMapping.data,
app: this.dataMappingProps.sources.app.id, this.dataMappingProps.sources.app.id,
record: record, context,
}); false
);
} else {
await doCreate(
this.dataMappingProps.dataMapping.data,
this.dataMappingProps.sources.app.id,
context
);
} }
} catch (error) { } catch (error) {
console.error("DataMappingAction error", error); console.error("DataMappingAction error", error);
@@ -127,40 +135,136 @@ export class DataMappingAction implements IAction {
new DataMappingAction(); new DataMappingAction();
const getValueByPath = (obj: any, path: string) => { const getContextVarByPath = (obj: any, path: string) => {
return path.split(".").reduce((o, k) => (o || {})[k], obj); return path.split(".").reduce((o, k) => (o || {})[k], obj);
}; };
type Resp = { records: RespRecordType[] }; interface UpdateRecord {
id: string;
type RespRecordType = { record: {
[key: string]: { [key: string]: {
type: string; value: any;
value: any; };
}; };
}; }
type Result = { const client = new KintoneRestAPIClient();
type: string;
value: any[];
};
const selectData = async (appid: string, field: string): Promise<Result> => { const doUpdate = async (
return kintone mappingData: Mapping[],
.api(kintone.api.url("/k/v1/records", true), "GET", { appId: string,
app: appid ?? kintone.app.getId(), context: any,
fields: [field], needCreate: boolean
}) ) => {
.then((resp: Resp) => { const targetField = await findUpdateField(mappingData, appId, context);
const result: Result = { type: "", value: [] }; console.log(targetField);
resp.records.forEach((element) => { if (targetField.records.length === 0 && needCreate) {
for (const [key, value] of Object.entries(element)) { await doCreate(mappingData, appId, context);
if (result.type === "") { } else {
result.type = value.type; // マッピングデータを単純なオブジェクトに処理し、ソース値が変数の場合は変数を置き換えます。
} const mappingRules = mappingData
result.value.push(value.value); .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,
});
}
}; };