diff --git a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
index 1f30e1d1b3..c4814550cd 100644
--- a/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectMoveResourceModal.vue
@@ -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),
diff --git a/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts b/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts
index 5ad62ed8e9..29a30f8a0c 100644
--- a/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts
+++ b/packages/editor-ui/src/components/Projects/ProjectNavigation.test.ts
@@ -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();
+ });
});
diff --git a/packages/editor-ui/src/components/Projects/ProjectNavigation.vue b/packages/editor-ui/src/components/Projects/ProjectNavigation.vue
index e14328b01e..a4cde7ea8c 100644
--- a/packages/editor-ui/src/components/Projects/ProjectNavigation.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectNavigation.vue
@@ -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"
/>
-
-
+
+
{{ locale.baseText('projects.menu.title') }}
@@ -155,9 +150,7 @@ onMounted(async () => {
/>
@@ -189,13 +182,7 @@ onMounted(async () => {
-
+
diff --git a/packages/editor-ui/src/components/Projects/ProjectSharing.vue b/packages/editor-ui/src/components/Projects/ProjectSharing.vue
index 788eec20cb..600d55892d 100644
--- a/packages/editor-ui/src/components/Projects/ProjectSharing.vue
+++ b/packages/editor-ui/src/components/Projects/ProjectSharing.vue
@@ -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) => {
diff --git a/packages/editor-ui/src/components/Workers/WorkerCard.ee.vue b/packages/editor-ui/src/components/Workers/WorkerCard.ee.vue
index c8c7c222c9..75096b7639 100644
--- a/packages/editor-ui/src/components/Workers/WorkerCard.ee.vue
+++ b/packages/editor-ui/src/components/Workers/WorkerCard.ee.vue
@@ -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 {
diff --git a/packages/editor-ui/src/utils/sortUtils.test.ts b/packages/editor-ui/src/utils/sortUtils.test.ts
new file mode 100644
index 0000000000..45a657df07
--- /dev/null
+++ b/packages/editor-ui/src/utils/sortUtils.test.ts
@@ -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 },
+ ]);
+ });
+});
diff --git a/packages/editor-ui/src/utils/sortUtils.ts b/packages/editor-ui/src/utils/sortUtils.ts
index 3d92d7a5e1..5e5534cdda 100644
--- a/packages/editor-ui/src/utils/sortUtils.ts
+++ b/packages/editor-ui/src/utils/sortUtils.ts
@@ -275,3 +275,16 @@ export function sublimeSearch(
return results;
}
+
+export const sortByProperty = (
+ 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;
+ });