fix(editor): Fix design system typecheck errors (no-changelog) (#9447)

This commit is contained in:
Alex Grozav
2024-05-21 20:53:19 +03:00
committed by GitHub
parent d21ad15c1f
commit eef5479e96
20 changed files with 161 additions and 65 deletions

View File

@@ -3,6 +3,7 @@ import { computed, ref } from 'vue';
import { uid } from '../../utils'; import { uid } from '../../utils';
import { ElColorPicker } from 'element-plus'; import { ElColorPicker } from 'element-plus';
import N8nInput from '../N8nInput'; import N8nInput from '../N8nInput';
import type { ElementPlusSizePropType } from '@/types';
export type ColorPickerProps = { export type ColorPickerProps = {
disabled?: boolean; disabled?: boolean;
@@ -19,10 +20,12 @@ export type ColorPickerProps = {
defineOptions({ name: 'N8nColorPicker' }); defineOptions({ name: 'N8nColorPicker' });
const props = withDefaults(defineProps<ColorPickerProps>(), { const props = withDefaults(defineProps<ColorPickerProps>(), {
disabled: false, disabled: false,
size: 'medium', size: 'default',
showAlpha: false, showAlpha: false,
colorFormat: 'hex', colorFormat: 'hex',
popperClass: '', popperClass: '',
predefine: undefined,
modelValue: undefined,
showInput: true, showInput: true,
name: uid('color-picker'), name: uid('color-picker'),
}); });
@@ -30,7 +33,7 @@ const props = withDefaults(defineProps<ColorPickerProps>(), {
const color = ref(props.modelValue); const color = ref(props.modelValue);
const colorPickerProps = computed(() => { const colorPickerProps = computed(() => {
const { showInput, ...rest } = props; const { showInput, modelValue, size, ...rest } = props;
return rest; return rest;
}); });
@@ -40,6 +43,8 @@ const emit = defineEmits<{
(event: 'active-change', value: string): void; (event: 'active-change', value: string): void;
}>(); }>();
const resolvedSize = computed(() => props.size as ElementPlusSizePropType);
const onChange = (value: string) => { const onChange = (value: string) => {
emit('change', value); emit('change', value);
}; };
@@ -61,7 +66,7 @@ const onColorSelect = (value: string) => {
<span :class="['n8n-color-picker', $style.component]"> <span :class="['n8n-color-picker', $style.component]">
<ElColorPicker <ElColorPicker
v-bind="colorPickerProps" v-bind="colorPickerProps"
size="default" :size="resolvedSize"
@change="onChange" @change="onChange"
@active-change="onActiveChange" @active-change="onActiveChange"
@update:model-value="onColorSelect" @update:model-value="onColorSelect"
@@ -70,7 +75,7 @@ const onColorSelect = (value: string) => {
v-if="showInput" v-if="showInput"
:class="$style.input" :class="$style.input"
:disabled="props.disabled" :disabled="props.disabled"
:size="props.size" :size="size"
:model-value="color" :model-value="color"
:name="name" :name="name"
type="text" type="text"

View File

@@ -64,7 +64,7 @@ exports[`components > N8nColorPicker > should render with input 1`] = `
<!--teleport end--> <!--teleport end-->
<div <div
class="el-input el-input--medium n8n-input input input" class="el-input el-input--default n8n-input input input"
data-v-dab78bb8="" data-v-dab78bb8=""
> >
<!-- input --> <!-- input -->
@@ -79,7 +79,6 @@ exports[`components > N8nColorPicker > should render with input 1`] = `
<input <input
autocomplete="off" autocomplete="off"
class="el-input__inner" class="el-input__inner"
maxlength="Infinity"
name="color-picker" name="color-picker"
placeholder="" placeholder=""
rows="2" rows="2"

View File

@@ -1,8 +1,11 @@
<template> <template>
<N8nCheckbox <N8nCheckbox
v-if="type === 'checkbox'" v-if="type === 'checkbox'"
v-bind="$props"
ref="inputRef" ref="inputRef"
:label="label"
:disabled="disabled"
:label-size="labelSize as CheckboxLabelSizePropType"
:model-value="modelValue as CheckboxModelValuePropType"
@update:model-value="onUpdateModelValue" @update:model-value="onUpdateModelValue"
@focus="onFocus" @focus="onFocus"
/> />
@@ -17,7 +20,7 @@
{{ tooltipText }} {{ tooltipText }}
</template> </template>
<ElSwitch <ElSwitch
:model-value="modelValue" :model-value="modelValue as SwitchModelValuePropType"
:active-color="activeColor" :active-color="activeColor"
:inactive-color="inactiveColor" :inactive-color="inactiveColor"
@update:model-value="onUpdateModelValue" @update:model-value="onUpdateModelValue"
@@ -59,9 +62,9 @@
v-else v-else
ref="inputRef" ref="inputRef"
:name="name" :name="name"
:type="type" :type="type as InputTypePropType"
:placeholder="placeholder" :placeholder="placeholder"
:model-value="modelValue" :model-value="modelValue as InputModelValuePropType"
:maxlength="maxlength" :maxlength="maxlength"
:autocomplete="autocomplete" :autocomplete="autocomplete"
:disabled="disabled" :disabled="disabled"
@@ -99,7 +102,18 @@ import N8nCheckbox from '../N8nCheckbox';
import { ElSwitch } from 'element-plus'; import { ElSwitch } from 'element-plus';
import { getValidationError, VALIDATORS } from './validators'; import { getValidationError, VALIDATORS } from './validators';
import type { Rule, RuleGroup, IValidator, Validatable, FormState } from '../../types'; import type {
Rule,
RuleGroup,
IValidator,
Validatable,
InputModelValuePropType,
InputTypePropType,
SwitchModelValuePropType,
CheckboxModelValuePropType,
CheckboxLabelSizePropType,
InputAutocompletePropType,
} from '../../types';
import { t } from '../../locale'; import { t } from '../../locale';
@@ -120,10 +134,10 @@ export interface Props {
validators?: { [key: string]: IValidator | RuleGroup }; validators?: { [key: string]: IValidator | RuleGroup };
maxlength?: number; maxlength?: number;
options?: Array<{ value: string | number; label: string; disabled?: boolean }>; options?: Array<{ value: string | number; label: string; disabled?: boolean }>;
autocomplete?: string; autocomplete?: InputAutocompletePropType;
name?: string; name?: string;
focusInitially?: boolean; focusInitially?: boolean;
labelSize?: 'small' | 'medium'; labelSize?: 'small' | 'medium' | 'large';
disabled?: boolean; disabled?: boolean;
activeLabel?: string; activeLabel?: string;
activeColor?: string; activeColor?: string;
@@ -206,7 +220,7 @@ function onBlur() {
$emit('blur'); $emit('blur');
} }
function onUpdateModelValue(value: FormState) { function onUpdateModelValue(value: Validatable) {
state.isTyping = true; state.isTyping = true;
$emit('update:modelValue', value); $emit('update:modelValue', value);
} }
@@ -225,9 +239,9 @@ const validationError = computed<string | null>(() => {
const error = getInputValidationError(); const error = getInputValidationError();
if (error) { if (error) {
if (error.messageKey) { if ('messageKey' in error) {
return t(error.messageKey, error.options); return t(error.messageKey, error.options as object);
} else { } else if ('message' in error) {
return error.message; return error.message;
} }
} }

View File

@@ -20,7 +20,7 @@ export const requiredValidator: IValidator<{}> = {
}; };
export const minLengthValidator: IValidator<{ minimum: number }> = { export const minLengthValidator: IValidator<{ minimum: number }> = {
validate: (value: Validatable, config: { minimum: number }) => { validate: (value: Validatable, config) => {
if (typeof value === 'string' && value.length < config.minimum) { if (typeof value === 'string' && value.length < config.minimum) {
return { return {
messageKey: 'formInput.validator.minCharactersRequired', messageKey: 'formInput.validator.minCharactersRequired',
@@ -76,7 +76,7 @@ export const emailValidator: IValidator<{}> = {
}; };
export const containsUpperCaseValidator: IValidator<{ minimum: number }> = { export const containsUpperCaseValidator: IValidator<{ minimum: number }> = {
validate: (value: Validatable, config: { minimum: number }) => { validate: (value: Validatable, config) => {
if (typeof value !== 'string') { if (typeof value !== 'string') {
return false; return false;
} }
@@ -94,7 +94,7 @@ export const containsUpperCaseValidator: IValidator<{ minimum: number }> = {
}; };
export const matchRegex: IValidator<{ regex: RegExp; message: string }> = { export const matchRegex: IValidator<{ regex: RegExp; message: string }> = {
validate: (value: Validatable, config: { regex: RegExp; message: string }) => { validate: (value: Validatable, config) => {
if (!config.regex.test(`${value as string}`)) { if (!config.regex.test(`${value as string}`)) {
return { return {
message: config.message, message: config.message,

View File

@@ -75,7 +75,7 @@ export default defineComponent({
default: true, default: true,
}, },
tagSize: { tagSize: {
type: String, type: String as PropType<'small' | 'medium'>,
default: 'small', default: 'small',
validator: (value: string): boolean => ['small', 'medium'].includes(value), validator: (value: string): boolean => ['small', 'medium'].includes(value),
}, },

View File

@@ -1,11 +1,19 @@
<template> <template>
<ElInput <ElInput
ref="innerInput" ref="innerInput"
:size="computedSize" :model-value="modelValue"
:type="type"
:size="resolvedSize"
:class="['n8n-input', ...classes]" :class="['n8n-input', ...classes]"
:autocomplete="autocomplete" :autocomplete="autocomplete"
:name="name" :name="name"
v-bind="{ ...$props, ...$attrs }" :placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
:clearable="clearable"
:rows="rows"
:title="title"
v-bind="$attrs"
> >
<template v-if="$slots.prepend" #prepend> <template v-if="$slots.prepend" #prepend>
<slot name="prepend" /> <slot name="prepend" />
@@ -27,6 +35,7 @@ import { computed, ref } from 'vue';
import { ElInput } from 'element-plus'; import { ElInput } from 'element-plus';
import { uid } from '../../utils'; import { uid } from '../../utils';
import type { InputSize, InputType } from '@/types/input'; import type { InputSize, InputType } from '@/types/input';
import type { ElementPlusSizePropType, InputAutocompletePropType } from '@/types';
interface InputProps { interface InputProps {
modelValue?: string | number; modelValue?: string | number;
@@ -40,12 +49,13 @@ interface InputProps {
maxlength?: number; maxlength?: number;
title?: string; title?: string;
name?: string; name?: string;
autocomplete?: 'off' | 'autocomplete'; autocomplete?: InputAutocompletePropType;
} }
defineOptions({ name: 'N8nInput' }); defineOptions({ name: 'N8nInput' });
const props = withDefaults(defineProps<InputProps>(), { const props = withDefaults(defineProps<InputProps>(), {
modelValue: '', modelValue: '',
type: 'text',
size: 'large', size: 'large',
placeholder: '', placeholder: '',
disabled: false, disabled: false,
@@ -58,7 +68,9 @@ const props = withDefaults(defineProps<InputProps>(), {
autocomplete: 'off', autocomplete: 'off',
}); });
const computedSize = computed(() => (props.size === 'xlarge' ? undefined : props.size)); const resolvedSize = computed(
() => (props.size === 'xlarge' ? undefined : props.size) as ElementPlusSizePropType,
);
const classes = computed(() => { const classes = computed(() => {
const applied: string[] = []; const applied: string[] = [];

View File

@@ -7,7 +7,7 @@ exports[`N8nInput > should render correctly 1`] = `
<!--v-if--> <!--v-if-->
<div class="el-input__wrapper"> <div class="el-input__wrapper">
<!-- prefix slot --> <!-- prefix slot -->
<!--v-if--><input class="el-input__inner" name="input" rows="2" maxlength="Infinity" title="" type="text" autocomplete="off" tabindex="0" placeholder=""><!-- suffix slot --> <!--v-if--><input class="el-input__inner" name="input" rows="2" title="" type="text" autocomplete="off" tabindex="0" placeholder=""><!-- suffix slot -->
<!--v-if--> <!--v-if-->
</div><!-- append slot --> </div><!-- append slot -->
<!--v-if--> <!--v-if-->

View File

@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { InputSize } from '@/types'; import type { ElementPlusSizePropType, InputSize } from '@/types';
import { ElInputNumber } from 'element-plus'; import { ElInputNumber } from 'element-plus';
import { computed } from 'vue';
type InputNumberProps = { type InputNumberProps = {
size?: InputSize; size?: InputSize;
@@ -10,9 +11,24 @@ type InputNumberProps = {
precision?: number; precision?: number;
}; };
defineProps<InputNumberProps>(); const props = withDefaults(defineProps<InputNumberProps>(), {
size: undefined,
step: 1,
precision: 0,
min: -Infinity,
max: Infinity,
});
const resolvedSize = computed(() => props.size as ElementPlusSizePropType);
</script> </script>
<template> <template>
<ElInputNumber v-bind="{ ...$props, ...$attrs }" /> <ElInputNumber
:size="resolvedSize"
:min="min"
:max="max"
:step="step"
:precision="precision"
v-bind="$attrs"
/>
</template> </template>

View File

@@ -17,7 +17,7 @@ import type { TextSize } from '@/types/text';
const THEME = ['primary', 'danger', 'text', 'secondary'] as const; const THEME = ['primary', 'danger', 'text', 'secondary'] as const;
interface LinkProps { interface LinkProps {
to?: RouteLocationRaw; to?: RouteLocationRaw | string;
size?: TextSize; size?: TextSize;
newWindow?: boolean; newWindow?: boolean;
bold?: boolean; bold?: boolean;
@@ -27,6 +27,8 @@ interface LinkProps {
defineOptions({ name: 'N8nLink' }); defineOptions({ name: 'N8nLink' });
withDefaults(defineProps<LinkProps>(), { withDefaults(defineProps<LinkProps>(), {
to: undefined,
size: undefined,
bold: false, bold: false,
underline: false, underline: false,
theme: 'primary', theme: 'primary',

View File

@@ -13,7 +13,7 @@
<template #content>{{ nodeTypeName }}</template> <template #content>{{ nodeTypeName }}</template>
<div v-if="type !== 'unknown'" :class="$style.icon"> <div v-if="type !== 'unknown'" :class="$style.icon">
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" /> <img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
<FontAwesomeIcon v-else :icon="name" :class="$style.iconFa" :style="fontStyleData" /> <FontAwesomeIcon v-else :icon="`${name}`" :class="$style.iconFa" :style="fontStyleData" />
</div> </div>
<div v-else :class="$style.nodeIconPlaceholder"> <div v-else :class="$style.nodeIconPlaceholder">
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }} {{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
@@ -22,7 +22,7 @@
<template v-else> <template v-else>
<div v-if="type !== 'unknown'" :class="$style.icon"> <div v-if="type !== 'unknown'" :class="$style.icon">
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" /> <img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
<FontAwesomeIcon v-else :icon="name" :style="fontStyleData" /> <FontAwesomeIcon v-else :icon="`${name}`" :style="fontStyleData" />
<div v-if="badge" :class="$style.badge" :style="badgeStyleData"> <div v-if="badge" :class="$style.badge" :style="badgeStyleData">
<n8n-node-icon :type="badge.type" :src="badge.src" :size="badgeSize"></n8n-node-icon> <n8n-node-icon :type="badge.type" :src="badge.src" :size="badgeSize"></n8n-node-icon>
</div> </div>

View File

@@ -1,8 +1,13 @@
<template> <template>
<router-link v-if="useRouterLink" :to="to" v-bind="$attrs"> <router-link v-if="useRouterLink && to" :to="to" v-bind="$attrs">
<slot></slot> <slot></slot>
</router-link> </router-link>
<a v-else :href="to" :target="openNewWindow ? '_blank' : '_self'" v-bind="$attrs"> <a
v-else
:href="to ? `${to}` : undefined"
:target="openNewWindow ? '_blank' : '_self'"
v-bind="$attrs"
>
<slot></slot> <slot></slot>
</a> </a>
</template> </template>
@@ -12,7 +17,7 @@ import { computed } from 'vue';
import { type RouteLocationRaw } from 'vue-router'; import { type RouteLocationRaw } from 'vue-router';
interface RouteProps { interface RouteProps {
to?: RouteLocationRaw; to?: RouteLocationRaw | string;
newWindow?: boolean; newWindow?: boolean;
} }

View File

@@ -32,6 +32,7 @@
import { ElSelect } from 'element-plus'; import { ElSelect } from 'element-plus';
import { type PropType, defineComponent } from 'vue'; import { type PropType, defineComponent } from 'vue';
import type { SelectSize } from '@/types'; import type { SelectSize } from '@/types';
import { isEventBindingElementAttribute } from '../../utils';
type InnerSelectRef = InstanceType<typeof ElSelect>; type InnerSelectRef = InstanceType<typeof ElSelect>;
@@ -86,13 +87,16 @@ export default defineComponent({
}, },
computed: { computed: {
listeners() { listeners() {
return Object.entries(this.$attrs).reduce<Record<string, () => {}>>((acc, [key, value]) => { return Object.entries(this.$attrs).reduce<Record<string, (...args: unknown[]) => {}>>(
if (/^on[A-Z]/.test(key)) { (acc, [key, value]) => {
if (isEventBindingElementAttribute(value, key)) {
acc[key] = value; acc[key] = value;
} }
return acc; return acc;
}, {}); },
{},
);
}, },
computedSize(): InnerSelectRef['$props']['size'] { computedSize(): InnerSelectRef['$props']['size'] {
if (this.size === 'medium') { if (this.size === 'medium') {

View File

@@ -13,8 +13,7 @@ export default {
}; };
const methods = { const methods = {
onReinvite: action('reinvite'), action: ({ action: actionName }: { action: string; userId: string }) => action(actionName),
onDelete: action('delete'),
}; };
const Template: StoryFn = (args, { argTypes }) => ({ const Template: StoryFn = (args, { argTypes }) => ({
@@ -23,8 +22,7 @@ const Template: StoryFn = (args, { argTypes }) => ({
components: { components: {
N8nUsersList, N8nUsersList,
}, },
template: template: '<n8n-users-list v-bind="args" :actions="actions" @action="action" />',
'<n8n-users-list v-bind="args" :actions="actions" @reinvite="onReinvite" @delete="onDelete" />',
methods, methods,
}); });

View File

@@ -52,6 +52,7 @@ interface UsersListProps {
const props = withDefaults(defineProps<UsersListProps>(), { const props = withDefaults(defineProps<UsersListProps>(), {
readonly: false, readonly: false,
currentUserId: '',
users: () => [], users: () => [],
actions: () => [], actions: () => [],
isSamlLoginEnabled: false, isSamlLoginEnabled: false,
@@ -101,11 +102,15 @@ const defaultGuard = () => true;
const getActions = (user: IUser): UserAction[] => { const getActions = (user: IUser): UserAction[] => {
if (user.isOwner) return []; if (user.isOwner) return [];
return props.actions.filter((action) => (action.guard || defaultGuard)(user)); return props.actions.filter((action) => (action.guard ?? defaultGuard)(user));
}; };
const $emit = defineEmits(['*']); const $emit = defineEmits(['action']);
const onUserAction = (user: IUser, action: string) => $emit(action, user.id); const onUserAction = (user: IUser, action: string) =>
$emit('action', {
action,
userId: user.id,
});
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View File

@@ -1,4 +1,4 @@
import type { VNode } from 'vue'; import type { Component, VNode } from 'vue';
export type DatatableRowDataType = string | number | boolean | null | undefined; export type DatatableRowDataType = string | number | boolean | null | undefined;
@@ -14,5 +14,5 @@ export interface DatatableColumn {
label: string; label: string;
classes?: string[]; classes?: string[];
width?: string; width?: string;
render?: (row: DatatableRow) => (() => VNode | VNode[]) | DatatableRowDataType; render?: Component | ((row: DatatableRow) => (() => VNode | VNode[]) | DatatableRowDataType);
} }

View File

@@ -11,7 +11,11 @@ export type IValidator<T = unknown> = {
validate: ( validate: (
value: Validatable, value: Validatable,
config: T, config: T,
) => false | { messageKey: string; message?: string; options?: unknown } | null; ) =>
| false
| { message: string; options?: unknown }
| { messageKey: string; options?: unknown }
| null;
}; };
export type FormState = { export type FormState = {
@@ -45,13 +49,7 @@ export type IFormInput = {
infoText?: string; infoText?: string;
placeholder?: string; placeholder?: string;
options?: Array<{ label: string; value: string; disabled?: boolean }>; options?: Array<{ label: string; value: string; disabled?: boolean }>;
autocomplete?: autocomplete?: InputAutocompletePropType;
| 'off'
| 'new-password'
| 'current-password'
| 'given-name'
| 'family-name'
| 'email'; // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
capitalize?: boolean; capitalize?: boolean;
focusInitially?: boolean; focusInitially?: boolean;
disabled?: boolean; disabled?: boolean;
@@ -72,3 +70,17 @@ export type IFormBoxConfig = {
redirectLink?: string; redirectLink?: string;
redirectText?: string; redirectText?: string;
}; };
export type CheckboxLabelSizePropType = 'small' | 'medium' | undefined;
export type CheckboxModelValuePropType = boolean | undefined;
export type SwitchModelValuePropType = boolean | undefined;
export type InputModelValuePropType = string | number | undefined;
export type InputTypePropType = 'number' | 'text' | 'email' | 'password' | 'textarea' | undefined;
export type InputAutocompletePropType =
| 'off'
| 'new-password'
| 'current-password'
| 'given-name'
| 'family-name'
| 'email'; // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
export type ElementPlusSizePropType = '' | 'small' | 'large' | 'default' | undefined;

View File

@@ -1,4 +1,5 @@
export * from './event-bus'; export * from './event-bus';
export * from './markdown'; export * from './markdown';
export * from './typeguards';
export * from './uid'; export * from './uid';
export * from './valueByPath'; export * from './valueByPath';

View File

@@ -0,0 +1,6 @@
export function isEventBindingElementAttribute(
_attributeValue: unknown,
attributeName: string,
): _attributeValue is (...args: unknown[]) => {} {
return /^on[A-Z]/.test(attributeName);
}

View File

@@ -11,7 +11,7 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"baseUrl": ".", "baseUrl": ".",
"types": ["vitest/globals"], "types": ["vitest/globals"],
"typeRoots": ["@testing-library", "@types", "../../node_modules"], "typeRoots": ["./node_modules/@testing-library", "./node_modules/@types", "../../node_modules", "../../node_modules/@types"],
"paths": { "paths": {
"@/*": ["src/*"] "@/*": ["src/*"]
}, },

View File

@@ -55,12 +55,7 @@
:users="usersStore.allUsers" :users="usersStore.allUsers"
:current-user-id="usersStore.currentUserId" :current-user-id="usersStore.currentUserId"
:is-saml-login-enabled="ssoStore.isSamlLoginEnabled" :is-saml-login-enabled="ssoStore.isSamlLoginEnabled"
@delete="onDelete" @action="onUsersListAction"
@reinvite="onReinvite"
@copy-invite-link="onCopyInviteLink"
@copy-password-reset-link="onCopyPasswordResetLink"
@allow-s-s-o-manual-login="onAllowSSOManualLogin"
@disallow-s-s-o-manual-login="onDisallowSSOManualLogin"
> >
<template #actions="{ user }"> <template #actions="{ user }">
<n8n-select <n8n-select
@@ -192,6 +187,28 @@ export default defineComponent({
} }
}, },
methods: { methods: {
async onUsersListAction({ action, userId }: { action: string; userId: string }) {
switch (action) {
case 'delete':
await this.onDelete(userId);
break;
case 'reinvite':
await this.onReinvite(userId);
break;
case 'copyInviteLink':
await this.onCopyInviteLink(userId);
break;
case 'copyPasswordResetLink':
await this.onCopyPasswordResetLink(userId);
break;
case 'allowSSOManualLogin':
await this.onAllowSSOManualLogin(userId);
break;
case 'disallowSSOManualLogin':
await this.onDisallowSSOManualLogin(userId);
break;
}
},
redirectToSetup() { redirectToSetup() {
void this.$router.push({ name: VIEWS.SETUP }); void this.$router.push({ name: VIEWS.SETUP });
}, },