fix(editor): Fix sorting problem in older browsers that don't support toSorted (#11204)

This commit is contained in:
Csaba Tuncsik 2024-10-17 14:11:34 +02:00 committed by GitHub
parent 83ca7f8e90
commit c728a2ffe0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 110 additions and 43 deletions

View file

@ -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),

View file

@ -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();
});
});

View file

@ -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>

View file

@ -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) => {

View file

@ -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 {

View 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 },
]);
});
});

View file

@ -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;
});