n8n/packages/editor-ui/src/components/ResourceMapper/MatchingColumnsSelect.vue
Milorad FIlipović e6cff3fce4
fix(editor): Fix Remove all fields not removing values in resource mapper (#6940)
Github issue / Community forum post (link here to close automatically):
2023-08-17 14:22:28 +02:00

215 lines
5.6 KiB
Vue

<script setup lang="ts">
import type {
INodeProperties,
INodePropertyTypeOptions,
ResourceMapperField,
ResourceMapperFields,
} from 'n8n-workflow';
import { computed, reactive, watch } from 'vue';
import { i18n as locale } from '@/plugins/i18n';
import { useNodeSpecificationValues } from '@/composables';
import ParameterOptions from '@/components/ParameterOptions.vue';
interface Props {
parameter: INodeProperties;
initialValue: string[];
fieldsToMap: ResourceMapperFields['fields'];
typeOptions: INodePropertyTypeOptions | undefined;
labelSize: string;
inputSize: string;
loading: boolean;
serviceName: string;
teleported?: boolean;
refreshInProgress: boolean;
}
const props = withDefaults(defineProps<Props>(), {
teleported: true,
});
const {
resourceMapperTypeOptions,
singularFieldWord,
singularFieldWordCapitalized,
pluralFieldWord,
pluralFieldWordCapitalized,
} = useNodeSpecificationValues(props.typeOptions);
// Depending on the mode (multiple/singe key column), the selected value can be a string or an array of strings
const state = reactive({
selected: props.initialValue as string[] | string,
});
watch(
() => props.initialValue,
() => {
state.selected =
resourceMapperTypeOptions.value?.multiKeyMatch === true
? props.initialValue
: props.initialValue[0];
},
);
const emit = defineEmits<{
(event: 'matchingColumnsChanged', value: string[]): void;
(event: 'refreshFieldList'): void;
}>();
const availableMatchingFields = computed<ResourceMapperField[]>(() => {
return props.fieldsToMap.filter((field) => {
return (field.canBeUsedToMatch || field.defaultMatch) && field.display !== false;
});
});
// Field label, description and tooltip: Labels here use content and field words defined in parameter type options
const fieldLabel = computed<string>(() => {
if (resourceMapperTypeOptions.value?.matchingFieldsLabels?.title) {
return resourceMapperTypeOptions.value.matchingFieldsLabels.title;
}
const fieldWord =
resourceMapperTypeOptions.value?.multiKeyMatch === true
? pluralFieldWordCapitalized.value
: singularFieldWordCapitalized.value;
return locale.baseText('resourceMapper.columnsToMatchOn.label', {
interpolate: {
fieldWord,
},
});
});
const fieldDescription = computed<string>(() => {
if (resourceMapperTypeOptions.value?.matchingFieldsLabels?.hint) {
return resourceMapperTypeOptions.value.matchingFieldsLabels.hint;
}
const labeli18nKey =
resourceMapperTypeOptions.value?.multiKeyMatch === true
? 'resourceMapper.columnsToMatchOn.multi.description'
: 'resourceMapper.columnsToMatchOn.single.description';
return locale.baseText(labeli18nKey, {
interpolate: {
fieldWord:
resourceMapperTypeOptions.value?.multiKeyMatch === true
? `${pluralFieldWord.value}`
: `${singularFieldWord.value}`,
},
});
});
const fieldTooltip = computed<string>(() => {
if (resourceMapperTypeOptions.value?.matchingFieldsLabels?.description) {
return resourceMapperTypeOptions.value.matchingFieldsLabels.description;
}
return locale.baseText('resourceMapper.columnsToMatchOn.tooltip', {
interpolate: {
fieldWord:
resourceMapperTypeOptions.value?.multiKeyMatch === true
? `${pluralFieldWord.value}`
: `${singularFieldWord.value}`,
},
});
});
const parameterActions = computed<Array<{ label: string; value: string; disabled?: boolean }>>(
() => {
return [
{
label: locale.baseText('resourceMapper.refreshFieldList', {
interpolate: { fieldWord: singularFieldWordCapitalized.value },
}),
value: 'refreshFieldList',
},
];
},
);
const fetchingFieldsLabel = computed<string>(() => {
return locale.baseText('resourceMapper.fetchingFields.message', {
interpolate: {
fieldWord: pluralFieldWord.value,
},
});
});
function onSelectionChange(value: string | string[]) {
if (resourceMapperTypeOptions.value?.multiKeyMatch === true) {
state.selected = value as string[];
} else {
state.selected = value as string;
}
emitValueChanged();
}
function emitValueChanged() {
if (state.selected) {
emit(
'matchingColumnsChanged',
Array.isArray(state.selected) ? state.selected : [state.selected],
);
}
}
function onParameterActionSelected(action: string): void {
switch (action) {
case 'refreshFieldList':
emit('refreshFieldList');
break;
default:
break;
}
}
defineExpose({
state,
});
</script>
<template>
<div class="mt-2xs" data-test-id="matching-column-select">
<n8n-input-label
v-if="availableMatchingFields.length > 0"
:label="fieldLabel"
:tooltipText="fieldTooltip"
:bold="false"
:required="false"
:size="labelSize"
color="text-dark"
>
<template #options>
<parameter-options
:parameter="parameter"
:customActions="parameterActions"
:loading="props.refreshInProgress"
:loadingMessage="fetchingFieldsLabel"
@update:modelValue="onParameterActionSelected"
/>
</template>
<n8n-select
:multiple="resourceMapperTypeOptions?.multiKeyMatch === true"
:modelValue="state.selected"
:size="props.inputSize"
:disabled="loading"
:teleported="teleported"
@update:modelValue="onSelectionChange"
>
<n8n-option
v-for="field in availableMatchingFields"
:key="field.id"
:value="field.id"
:data-test-id="`matching-column-option-${field.id}`"
>
{{ field.displayName }}
</n8n-option>
</n8n-select>
<n8n-text size="small">
{{ fieldDescription }}
</n8n-text>
</n8n-input-label>
<n8n-notice v-else>
{{
locale.baseText('resourceMapper.columnsToMatchOn.noFieldsFound', {
interpolate: { fieldWord: singularFieldWord, serviceName: props.serviceName },
})
}}
</n8n-notice>
</div>
</template>