mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
fix(editor): Fix sorting problem in older browsers that don't support toSorted
(#11204)
This commit is contained in:
parent
83ca7f8e90
commit
c728a2ffe0
|
@ -12,6 +12,7 @@ import { ProjectTypes } from '@/types/projects.types';
|
|||
import ProjectMoveSuccessToastMessage from '@/components/Projects/ProjectMoveSuccessToastMessage.vue';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { getResourcePermissions } from '@/permissions';
|
||||
import { sortByProperty } from '@/utils/sortUtils';
|
||||
|
||||
const props = defineProps<{
|
||||
modalName: string;
|
||||
|
@ -34,14 +35,15 @@ const processedName = computed(
|
|||
() => processProjectName(props.data.resource.homeProject?.name ?? '') ?? '',
|
||||
);
|
||||
const availableProjects = computed(() =>
|
||||
projectsStore.availableProjects
|
||||
.filter(
|
||||
sortByProperty(
|
||||
'name',
|
||||
projectsStore.availableProjects.filter(
|
||||
(p) =>
|
||||
p.name?.toLowerCase().includes(filter.value.toLowerCase()) &&
|
||||
p.id !== props.data.resource.homeProject?.id &&
|
||||
(!p.scopes || getResourcePermissions(p.scopes)[props.data.resourceType].create),
|
||||
)
|
||||
.sort((a, b) => a.name?.localeCompare(b.name ?? '') ?? 0),
|
||||
),
|
||||
),
|
||||
);
|
||||
const selectedProject = computed(() =>
|
||||
availableProjects.value.find((p) => p.id === projectId.value),
|
||||
|
|
|
@ -172,4 +172,16 @@ describe('ProjectsNavigation', () => {
|
|||
|
||||
expect(queryByRole('heading', { level: 3, name: 'Projects' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show "Projects" title when there are no available projects', async () => {
|
||||
projectsStore.myProjects = [];
|
||||
|
||||
const { queryByRole } = renderComponent({
|
||||
props: {
|
||||
collapsed: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(queryByRole('heading', { level: 3, name: 'Projects' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ import { useProjectsStore } from '@/stores/projects.store';
|
|||
import type { ProjectListItem } from '@/types/projects.types';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { sortByProperty } from '@/utils/sortUtils';
|
||||
|
||||
type Props = {
|
||||
collapsed: boolean;
|
||||
|
@ -86,21 +87,16 @@ const addProjectClicked = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
const displayProjects = computed(() => {
|
||||
return projectsStore.myProjects
|
||||
.filter((p) => p.type === 'team')
|
||||
.toSorted((a, b) => {
|
||||
if (!a.name || !b.name) {
|
||||
return 0;
|
||||
}
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
} else if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
const displayProjects = computed(() =>
|
||||
sortByProperty(
|
||||
'name',
|
||||
projectsStore.myProjects.filter((p) => p.type === 'team'),
|
||||
),
|
||||
);
|
||||
|
||||
const canCreateProjects = computed(
|
||||
() => projectsStore.hasPermissionToCreateProjects && projectsStore.isTeamProjectFeatureEnabled,
|
||||
);
|
||||
|
||||
const goToUpgrade = async () => {
|
||||
await uiStore.goToUpgrade('rbac', 'upgrade-rbac');
|
||||
|
@ -123,14 +119,13 @@ onMounted(async () => {
|
|||
data-test-id="project-home-menu-item"
|
||||
/>
|
||||
</ElMenu>
|
||||
<hr
|
||||
v-if="
|
||||
displayProjects.length ||
|
||||
(projectsStore.hasPermissionToCreateProjects && projectsStore.isTeamProjectFeatureEnabled)
|
||||
"
|
||||
class="mt-m mb-m"
|
||||
/>
|
||||
<N8nText v-if="!props.collapsed" :class="$style.projectsLabel" tag="h3" bold>
|
||||
<hr v-if="displayProjects.length || canCreateProjects" class="mt-m mb-m" />
|
||||
<N8nText
|
||||
v-if="!props.collapsed && displayProjects.length"
|
||||
:class="$style.projectsLabel"
|
||||
tag="h3"
|
||||
bold
|
||||
>
|
||||
<span>{{ locale.baseText('projects.menu.title') }}</span>
|
||||
</N8nText>
|
||||
<ElMenu v-if="displayProjects.length" :collapse="props.collapsed" :class="$style.projectItems">
|
||||
|
@ -155,9 +150,7 @@ onMounted(async () => {
|
|||
/>
|
||||
</ElMenu>
|
||||
<N8nTooltip
|
||||
v-if="
|
||||
projectsStore.hasPermissionToCreateProjects && projectsStore.isTeamProjectFeatureEnabled
|
||||
"
|
||||
v-if="canCreateProjects"
|
||||
placement="right"
|
||||
:disabled="projectsStore.canCreateProjects"
|
||||
>
|
||||
|
@ -189,13 +182,7 @@ onMounted(async () => {
|
|||
</i18n-t>
|
||||
</template>
|
||||
</N8nTooltip>
|
||||
<hr
|
||||
v-if="
|
||||
displayProjects.length ||
|
||||
(projectsStore.hasPermissionToCreateProjects && projectsStore.isTeamProjectFeatureEnabled)
|
||||
"
|
||||
class="mt-m mb-m"
|
||||
/>
|
||||
<hr v-if="displayProjects.length || canCreateProjects" class="mt-m mb-m" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useI18n } from '@/composables/useI18n';
|
|||
import type { ProjectListItem, ProjectSharingData } from '@/types/projects.types';
|
||||
import ProjectSharingInfo from '@/components/Projects/ProjectSharingInfo.vue';
|
||||
import type { RoleMap } from '@/types/roles.types';
|
||||
import { sortByProperty } from '@/utils/sortUtils';
|
||||
|
||||
const locale = useI18n();
|
||||
|
||||
|
@ -35,13 +36,14 @@ const noDataText = computed(
|
|||
() => props.emptyOptionsText ?? locale.baseText('projects.sharing.noMatchingUsers'),
|
||||
);
|
||||
const filteredProjects = computed(() =>
|
||||
props.projects
|
||||
.filter(
|
||||
sortByProperty(
|
||||
'name',
|
||||
props.projects.filter(
|
||||
(project) =>
|
||||
project.name?.toLowerCase().includes(filter.value.toLowerCase()) &&
|
||||
(Array.isArray(model.value) ? !model.value?.find((p) => p.id === project.id) : true),
|
||||
)
|
||||
.sort((a, b) => (a.name && b.name ? a.name.localeCompare(b.name) : 0)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const setFilter = (query: string) => {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { averageWorkerLoadFromLoadsAsString, memAsGb } from '../../utils/workerU
|
|||
import WorkerJobAccordion from './WorkerJobAccordion.ee.vue';
|
||||
import WorkerNetAccordion from './WorkerNetAccordion.ee.vue';
|
||||
import WorkerChartsAccordion from './WorkerChartsAccordion.ee.vue';
|
||||
import { sortByProperty } from '@/utils/sortUtils';
|
||||
|
||||
let interval: NodeJS.Timer;
|
||||
|
||||
|
@ -23,8 +24,8 @@ const worker = computed((): WorkerStatus | undefined => {
|
|||
return orchestrationStore.getWorkerStatus(props.workerId);
|
||||
});
|
||||
|
||||
const sortedWorkerInterfaces = computed(
|
||||
() => worker.value?.interfaces.toSorted((a, b) => a.family.localeCompare(b.family)) ?? [],
|
||||
const sortedWorkerInterfaces = computed(() =>
|
||||
sortByProperty('family', worker.value?.interfaces.slice() ?? []),
|
||||
);
|
||||
|
||||
function upTime(seconds: number): string {
|
||||
|
|
50
packages/editor-ui/src/utils/sortUtils.test.ts
Normal file
50
packages/editor-ui/src/utils/sortUtils.test.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { sortByProperty } from '@/utils/sortUtils';
|
||||
|
||||
const arrayOfObjects = [
|
||||
{ name: 'Álvaro', age: 30 },
|
||||
{ name: 'Élodie', age: 28 },
|
||||
{ name: 'Željko', age: 25 },
|
||||
{ name: 'Bob', age: 35 },
|
||||
];
|
||||
|
||||
describe('sortUtils', () => {
|
||||
it('"sortByProperty" should sort an array of objects by a property', () => {
|
||||
const sortedArray = sortByProperty('name', arrayOfObjects);
|
||||
expect(sortedArray).toEqual([
|
||||
{ name: 'Álvaro', age: 30 },
|
||||
{ name: 'Bob', age: 35 },
|
||||
{ name: 'Élodie', age: 28 },
|
||||
{ name: 'Željko', age: 25 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('"sortByProperty" should sort an array of objects by a property in descending order', () => {
|
||||
const sortedArray = sortByProperty('name', arrayOfObjects, 'desc');
|
||||
expect(sortedArray).toEqual([
|
||||
{ name: 'Željko', age: 25 },
|
||||
{ name: 'Élodie', age: 28 },
|
||||
{ name: 'Bob', age: 35 },
|
||||
{ name: 'Álvaro', age: 30 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('"sortByProperty" should sort an array of objects by a property if its number', () => {
|
||||
const sortedArray = sortByProperty('age', arrayOfObjects);
|
||||
expect(sortedArray).toEqual([
|
||||
{ name: 'Željko', age: 25 },
|
||||
{ name: 'Élodie', age: 28 },
|
||||
{ name: 'Álvaro', age: 30 },
|
||||
{ name: 'Bob', age: 35 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('"sortByProperty" should sort an array of objects by a property in descending order if its number', () => {
|
||||
const sortedArray = sortByProperty('age', arrayOfObjects, 'desc');
|
||||
expect(sortedArray).toEqual([
|
||||
{ name: 'Bob', age: 35 },
|
||||
{ name: 'Álvaro', age: 30 },
|
||||
{ name: 'Élodie', age: 28 },
|
||||
{ name: 'Željko', age: 25 },
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -275,3 +275,16 @@ export function sublimeSearch<T extends object>(
|
|||
|
||||
return results;
|
||||
}
|
||||
|
||||
export const sortByProperty = <T>(
|
||||
property: keyof T,
|
||||
arr: T[],
|
||||
order: 'asc' | 'desc' = 'asc',
|
||||
): T[] =>
|
||||
arr.sort((a, b) => {
|
||||
const result = String(a[property]).localeCompare(String(b[property]), undefined, {
|
||||
numeric: true,
|
||||
sensitivity: 'base',
|
||||
});
|
||||
return order === 'asc' ? result : -result;
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue