fix(editor): Type errors in VariablesView.vue (no-changelog) (#9558)

This commit is contained in:
Ricardo Espinoza
2024-06-03 13:17:01 -04:00
committed by GitHub
parent 08d9c9a787
commit 631f077c18
6 changed files with 79 additions and 58 deletions

View File

@@ -1661,15 +1661,11 @@ export declare namespace DynamicNodeParameters {
} }
export interface EnvironmentVariable { export interface EnvironmentVariable {
id: number; id: string;
key: string; key: string;
value: string; value: string;
} }
export interface TemporaryEnvironmentVariable extends Omit<EnvironmentVariable, 'id'> {
id: string;
}
export type ExecutionFilterMetadata = { export type ExecutionFilterMetadata = {
key: string; key: string;
value: string; value: string;

View File

@@ -16,14 +16,14 @@ export async function getVariable(
export async function createVariable( export async function createVariable(
context: IRestApiContext, context: IRestApiContext,
data: Omit<EnvironmentVariable, 'id'>, data: Omit<EnvironmentVariable, 'id'>,
) { ): Promise<EnvironmentVariable> {
return await makeRestApiRequest(context, 'POST', '/variables', data as unknown as IDataObject); return await makeRestApiRequest(context, 'POST', '/variables', data as unknown as IDataObject);
} }
export async function updateVariable( export async function updateVariable(
context: IRestApiContext, context: IRestApiContext,
{ id, ...data }: EnvironmentVariable, { id, ...data }: EnvironmentVariable,
) { ): Promise<EnvironmentVariable> {
return await makeRestApiRequest( return await makeRestApiRequest(
context, context,
'PATCH', 'PATCH',

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ComponentPublicInstance, PropType } from 'vue'; import type { ComponentPublicInstance, PropType } from 'vue';
import { computed, nextTick, onMounted, ref, watch } from 'vue'; import { computed, nextTick, onMounted, ref, watch } from 'vue';
import type { EnvironmentVariable, Rule, RuleGroup } from '@/Interface'; import type { Rule, RuleGroup } from '@/Interface';
import { useI18n } from '@/composables/useI18n'; import { useI18n } from '@/composables/useI18n';
import { useToast } from '@/composables/useToast'; import { useToast } from '@/composables/useToast';
import { useClipboard } from '@/composables/useClipboard'; import { useClipboard } from '@/composables/useClipboard';
@@ -9,6 +9,7 @@ import { EnterpriseEditionFeature } from '@/constants';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { getVariablesPermissions } from '@/permissions'; import { getVariablesPermissions } from '@/permissions';
import type { IResource } from './layouts/ResourcesListLayout.vue';
const i18n = useI18n(); const i18n = useI18n();
const clipboard = useClipboard(); const clipboard = useClipboard();
@@ -20,7 +21,7 @@ const emit = defineEmits(['save', 'cancel', 'edit', 'delete']);
const props = defineProps({ const props = defineProps({
data: { data: {
type: Object as PropType<EnvironmentVariable>, type: Object as PropType<IResource>,
default: () => ({}), default: () => ({}),
}, },
editing: { editing: {
@@ -30,20 +31,20 @@ const props = defineProps({
}); });
const permissions = computed(() => getVariablesPermissions(usersStore.currentUser)); const permissions = computed(() => getVariablesPermissions(usersStore.currentUser));
const modelValue = ref<EnvironmentVariable>({ ...props.data }); const modelValue = ref<IResource>({ ...props.data });
const formValidationStatus = ref<Record<string, boolean>>({ const formValidationStatus = ref<Record<string, boolean>>({
key: false, key: false,
value: false, value: false,
}); });
const formValid = computed(() => { const formValid = computed(() => {
return formValidationStatus.value.key && formValidationStatus.value.value; return formValidationStatus.value.name && formValidationStatus.value.value;
}); });
const keyInputRef = ref<ComponentPublicInstance & { inputRef?: HTMLElement }>(); const keyInputRef = ref<ComponentPublicInstance & { inputRef?: HTMLElement }>();
const valueInputRef = ref<HTMLElement>(); const valueInputRef = ref<HTMLElement>();
const usage = ref(`$vars.${props.data.key}`); const usage = ref(`$vars.${props.data.name}`);
const isFeatureEnabled = computed(() => const isFeatureEnabled = computed(() =>
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables), settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables),
@@ -77,17 +78,17 @@ const valueValidationRules: Array<Rule | RuleGroup> = [
]; ];
watch( watch(
() => modelValue.value.key, () => modelValue.value.name,
async () => { async () => {
await nextTick(); await nextTick();
if (formValidationStatus.value.key) { if (formValidationStatus.value.name) {
updateUsageSyntax(); updateUsageSyntax();
} }
}, },
); );
function updateUsageSyntax() { function updateUsageSyntax() {
usage.value = `$vars.${modelValue.value.key || props.data.key}`; usage.value = `$vars.${modelValue.value.name || props.data.name}`;
} }
async function onCancel() { async function onCancel() {
@@ -111,8 +112,8 @@ async function onDelete() {
emit('delete', modelValue.value); emit('delete', modelValue.value);
} }
function onValidate(key: string, value: boolean) { function onValidate(name: string, value: boolean) {
formValidationStatus.value[key] = value; formValidationStatus.value[name] = value;
} }
function onUsageClick() { function onUsageClick() {
@@ -132,19 +133,19 @@ function focusFirstInput() {
<tr :class="$style.variablesRow" data-test-id="variables-row"> <tr :class="$style.variablesRow" data-test-id="variables-row">
<td class="variables-key-column"> <td class="variables-key-column">
<div> <div>
<span v-if="!editing">{{ data.key }}</span> <span v-if="!editing">{{ data.name }}</span>
<n8n-form-input <n8n-form-input
v-else v-else
ref="keyInputRef" ref="keyInputRef"
v-model="modelValue.key" v-model="modelValue.name"
label label
name="key" name="name"
data-test-id="variable-row-key-input" data-test-id="variable-row-key-input"
:placeholder="i18n.baseText('variables.editing.key.placeholder')" :placeholder="i18n.baseText('variables.editing.key.placeholder')"
required required
validate-on-blur validate-on-blur
:validation-rules="keyValidationRules" :validation-rules="keyValidationRules"
@validate="(value: boolean) => onValidate('key', value)" @validate="(value: boolean) => onValidate('name', value)"
/> />
</div> </div>
</td> </td>
@@ -168,7 +169,7 @@ function focusFirstInput() {
<td class="variables-usage-column"> <td class="variables-usage-column">
<div> <div>
<n8n-tooltip placement="top"> <n8n-tooltip placement="top">
<span v-if="modelValue.key && usage" :class="$style.usageSyntax" @click="onUsageClick">{{ <span v-if="modelValue.name && usage" :class="$style.usageSyntax" @click="onUsageClick">{{
usage usage
}}</span> }}</span>
<template #content> <template #content>

View File

@@ -1,5 +1,4 @@
import VariablesRow from '../VariablesRow.vue'; import VariablesRow from '../VariablesRow.vue';
import type { EnvironmentVariable } from '@/Interface';
import { fireEvent } from '@testing-library/vue'; import { fireEvent } from '@testing-library/vue';
import { setupServer } from '@/__tests__/server'; import { setupServer } from '@/__tests__/server';
import { afterAll, beforeAll } from 'vitest'; import { afterAll, beforeAll } from 'vitest';
@@ -42,9 +41,9 @@ describe('VariablesRow', () => {
server.shutdown(); server.shutdown();
}); });
const environmentVariable: EnvironmentVariable = { const environmentVariable = {
id: 1, id: '1',
key: 'key', name: 'key',
value: 'value', value: 'value',
}; };
@@ -83,7 +82,7 @@ describe('VariablesRow', () => {
expect(wrapper.getByTestId('variable-row-key-input')).toBeVisible(); expect(wrapper.getByTestId('variable-row-key-input')).toBeVisible();
expect(wrapper.getByTestId('variable-row-key-input').querySelector('input')).toHaveValue( expect(wrapper.getByTestId('variable-row-key-input').querySelector('input')).toHaveValue(
environmentVariable.key, environmentVariable.name,
); );
expect(wrapper.getByTestId('variable-row-value-input')).toBeVisible(); expect(wrapper.getByTestId('variable-row-value-input')).toBeVisible();
expect(wrapper.getByTestId('variable-row-value-input').querySelector('input')).toHaveValue( expect(wrapper.getByTestId('variable-row-value-input').querySelector('input')).toHaveValue(

View File

@@ -163,8 +163,9 @@ import type { BaseTextKey } from '@/plugins/i18n';
export interface IResource { export interface IResource {
id: string; id: string;
name: string; name: string;
updatedAt: string; value: string;
createdAt: string; updatedAt?: string;
createdAt?: string;
homeProject?: ProjectSharingData; homeProject?: ProjectSharingData;
} }

View File

@@ -10,17 +10,15 @@ import { useTelemetry } from '@/composables/useTelemetry';
import { useToast } from '@/composables/useToast'; import { useToast } from '@/composables/useToast';
import { useMessage } from '@/composables/useMessage'; import { useMessage } from '@/composables/useMessage';
import type { IResource } from '@/components/layouts/ResourcesListLayout.vue';
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue'; import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
import VariablesRow from '@/components/VariablesRow.vue'; import VariablesRow from '@/components/VariablesRow.vue';
import { EnterpriseEditionFeature, MODAL_CONFIRM } from '@/constants'; import { EnterpriseEditionFeature, MODAL_CONFIRM } from '@/constants';
import type { import type { DatatableColumn, EnvironmentVariable } from '@/Interface';
DatatableColumn,
EnvironmentVariable,
TemporaryEnvironmentVariable,
} from '@/Interface';
import { uid } from 'n8n-design-system/utils'; import { uid } from 'n8n-design-system/utils';
import { getVariablesPermissions } from '@/permissions'; import { getVariablesPermissions } from '@/permissions';
import type { BaseTextKey } from '@/plugins/i18n';
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const environmentsStore = useEnvironmentsStore(); const environmentsStore = useEnvironmentsStore();
@@ -38,7 +36,7 @@ const { showError } = useToast();
const TEMPORARY_VARIABLE_UID_BASE = '@tmpvar'; const TEMPORARY_VARIABLE_UID_BASE = '@tmpvar';
const allVariables = ref<Array<EnvironmentVariable | TemporaryEnvironmentVariable>>([]); const allVariables = ref<EnvironmentVariable[]>([]);
const editMode = ref<Record<string, boolean>>({}); const editMode = ref<Record<string, boolean>>({});
const permissions = getVariablesPermissions(usersStore.currentUser); const permissions = getVariablesPermissions(usersStore.currentUser);
@@ -46,6 +44,11 @@ const permissions = getVariablesPermissions(usersStore.currentUser);
const isFeatureEnabled = computed(() => const isFeatureEnabled = computed(() =>
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables), settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables),
); );
const variablesToResources = computed((): IResource[] =>
allVariables.value.map((v) => ({ id: v.id, name: v.key, value: v.value })),
);
const canCreateVariables = computed(() => isFeatureEnabled.value && permissions.create); const canCreateVariables = computed(() => isFeatureEnabled.value && permissions.create);
const datatableColumns = computed<DatatableColumn[]>(() => [ const datatableColumns = computed<DatatableColumn[]>(() => [
@@ -80,9 +83,9 @@ const datatableColumns = computed<DatatableColumn[]>(() => [
const contextBasedTranslationKeys = computed(() => uiStore.contextBasedTranslationKeys); const contextBasedTranslationKeys = computed(() => uiStore.contextBasedTranslationKeys);
const newlyAddedVariableIds = ref<number[]>([]); const newlyAddedVariableIds = ref<string[]>([]);
const nameSortFn = (a: EnvironmentVariable, b: EnvironmentVariable, direction: 'asc' | 'desc') => { const nameSortFn = (a: IResource, b: IResource, direction: 'asc' | 'desc') => {
if (`${a.id}`.startsWith(TEMPORARY_VARIABLE_UID_BASE)) { if (`${a.id}`.startsWith(TEMPORARY_VARIABLE_UID_BASE)) {
return -1; return -1;
} else if (`${b.id}`.startsWith(TEMPORARY_VARIABLE_UID_BASE)) { } else if (`${b.id}`.startsWith(TEMPORARY_VARIABLE_UID_BASE)) {
@@ -103,10 +106,10 @@ const nameSortFn = (a: EnvironmentVariable, b: EnvironmentVariable, direction: '
: displayName(b).trim().localeCompare(displayName(a).trim()); : displayName(b).trim().localeCompare(displayName(a).trim());
}; };
const sortFns = { const sortFns = {
nameAsc: (a: EnvironmentVariable, b: EnvironmentVariable) => { nameAsc: (a: IResource, b: IResource) => {
return nameSortFn(a, b, 'asc'); return nameSortFn(a, b, 'asc');
}, },
nameDesc: (a: EnvironmentVariable, b: EnvironmentVariable) => { nameDesc: (a: IResource, b: IResource) => {
return nameSortFn(a, b, 'desc'); return nameSortFn(a, b, 'desc');
}, },
}; };
@@ -115,6 +118,14 @@ function resetNewVariablesList() {
newlyAddedVariableIds.value = []; newlyAddedVariableIds.value = [];
} }
const resourceToEnvironmentVariable = (data: IResource): EnvironmentVariable => {
return {
id: data.id,
key: data.name,
value: data.value,
};
};
async function initialize() { async function initialize() {
if (!isFeatureEnabled.value) return; if (!isFeatureEnabled.value) return;
await environmentsStore.fetchAllVariables(); await environmentsStore.fetchAllVariables();
@@ -123,7 +134,7 @@ async function initialize() {
} }
function addTemporaryVariable() { function addTemporaryVariable() {
const temporaryVariable: TemporaryEnvironmentVariable = { const temporaryVariable: EnvironmentVariable = {
id: uid(TEMPORARY_VARIABLE_UID_BASE), id: uid(TEMPORARY_VARIABLE_UID_BASE),
key: '', key: '',
value: '', value: '',
@@ -132,7 +143,7 @@ function addTemporaryVariable() {
if (layoutRef.value) { if (layoutRef.value) {
// Reset scroll position // Reset scroll position
if (layoutRef.value.$refs.listWrapperRef) { if (layoutRef.value.$refs.listWrapperRef) {
layoutRef.value.$refs.listWrapperRef.scrollTop = 0; (layoutRef.value.$refs.listWrapperRef as HTMLDivElement).scrollTop = 0;
} }
// Reset pagination // Reset pagination
@@ -147,18 +158,19 @@ function addTemporaryVariable() {
telemetry.track('User clicked add variable button'); telemetry.track('User clicked add variable button');
} }
async function saveVariable(data: EnvironmentVariable | TemporaryEnvironmentVariable) { async function saveVariable(data: IResource) {
let updatedVariable: EnvironmentVariable; let updatedVariable: EnvironmentVariable;
const variable = resourceToEnvironmentVariable(data);
try { try {
if (typeof data.id === 'string' && data.id.startsWith(TEMPORARY_VARIABLE_UID_BASE)) { if (typeof data.id === 'string' && data.id.startsWith(TEMPORARY_VARIABLE_UID_BASE)) {
const { id, ...rest } = data; const { id, ...rest } = variable;
updatedVariable = await environmentsStore.createVariable(rest); updatedVariable = await environmentsStore.createVariable(rest);
allVariables.value.unshift(updatedVariable); allVariables.value.unshift(updatedVariable);
allVariables.value = allVariables.value.filter((variable) => variable.id !== data.id); allVariables.value = allVariables.value.filter((variable) => variable.id !== data.id);
newlyAddedVariableIds.value.unshift(updatedVariable.id); newlyAddedVariableIds.value.unshift(updatedVariable.id);
} else { } else {
updatedVariable = await environmentsStore.updateVariable(data as EnvironmentVariable); updatedVariable = await environmentsStore.updateVariable(variable);
allVariables.value = allVariables.value.filter((variable) => variable.id !== data.id); allVariables.value = allVariables.value.filter((variable) => variable.id !== data.id);
allVariables.value.push(updatedVariable); allVariables.value.push(updatedVariable);
toggleEditing(updatedVariable); toggleEditing(updatedVariable);
@@ -175,11 +187,11 @@ function toggleEditing(data: EnvironmentVariable) {
}; };
} }
function cancelEditing(data: EnvironmentVariable | TemporaryEnvironmentVariable) { function cancelEditing(data: EnvironmentVariable) {
if (typeof data.id === 'string' && data.id.startsWith(TEMPORARY_VARIABLE_UID_BASE)) { if (typeof data.id === 'string' && data.id.startsWith(TEMPORARY_VARIABLE_UID_BASE)) {
allVariables.value = allVariables.value.filter((variable) => variable.id !== data.id); allVariables.value = allVariables.value.filter((variable) => variable.id !== data.id);
} else { } else {
toggleEditing(data as EnvironmentVariable); toggleEditing(data);
} }
} }
@@ -209,8 +221,8 @@ function goToUpgrade() {
void uiStore.goToUpgrade('variables', 'upgrade-variables'); void uiStore.goToUpgrade('variables', 'upgrade-variables');
} }
function displayName(resource: EnvironmentVariable) { function displayName(resource: IResource) {
return resource.key; return resource.name;
} }
onBeforeMount(() => { onBeforeMount(() => {
@@ -234,7 +246,7 @@ onBeforeUnmount(() => {
class="variables-view" class="variables-view"
resource-key="variables" resource-key="variables"
:disabled="!isFeatureEnabled" :disabled="!isFeatureEnabled"
:resources="allVariables" :resources="variablesToResources"
:initialize="initialize" :initialize="initialize"
:shareable="false" :shareable="false"
:display-name="displayName" :display-name="displayName"
@@ -277,11 +289,17 @@ onBeforeUnmount(() => {
class="mb-m" class="mb-m"
data-test-id="unavailable-resources-list" data-test-id="unavailable-resources-list"
emoji="👋" emoji="👋"
:heading="$locale.baseText(contextBasedTranslationKeys.variables.unavailable.title)" :heading="
:description=" $locale.baseText(contextBasedTranslationKeys.variables.unavailable.title as BaseTextKey)
$locale.baseText(contextBasedTranslationKeys.variables.unavailable.description) "
:description="
$locale.baseText(
contextBasedTranslationKeys.variables.unavailable.description as BaseTextKey,
)
"
:button-text="
$locale.baseText(contextBasedTranslationKeys.variables.unavailable.button as BaseTextKey)
" "
:button-text="$locale.baseText(contextBasedTranslationKeys.variables.unavailable.button)"
button-type="secondary" button-type="secondary"
@click:button="goToUpgrade" @click:button="goToUpgrade"
/> />
@@ -291,11 +309,17 @@ onBeforeUnmount(() => {
v-if="!isFeatureEnabled" v-if="!isFeatureEnabled"
data-test-id="unavailable-resources-list" data-test-id="unavailable-resources-list"
emoji="👋" emoji="👋"
:heading="$locale.baseText(contextBasedTranslationKeys.variables.unavailable.title)" :heading="
:description=" $locale.baseText(contextBasedTranslationKeys.variables.unavailable.title as BaseTextKey)
$locale.baseText(contextBasedTranslationKeys.variables.unavailable.description) "
:description="
$locale.baseText(
contextBasedTranslationKeys.variables.unavailable.description as BaseTextKey,
)
"
:button-text="
$locale.baseText(contextBasedTranslationKeys.variables.unavailable.button as BaseTextKey)
" "
:button-text="$locale.baseText(contextBasedTranslationKeys.variables.unavailable.button)"
button-type="secondary" button-type="secondary"
@click:button="goToUpgrade" @click:button="goToUpgrade"
/> />
@@ -305,7 +329,7 @@ onBeforeUnmount(() => {
emoji="👋" emoji="👋"
:heading=" :heading="
$locale.baseText('variables.empty.notAllowedToCreate.heading', { $locale.baseText('variables.empty.notAllowedToCreate.heading', {
interpolate: { name: usersStore.currentUser.firstName }, interpolate: { name: usersStore.currentUser?.firstName ?? '' },
}) })
" "
:description="$locale.baseText('variables.empty.notAllowedToCreate.description')" :description="$locale.baseText('variables.empty.notAllowedToCreate.description')"