Skip to content
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
a55f2b2
functionality for add/edit/delete/move items as admin
MatteoGuarnaccia5 Oct 6, 2025
4b48a4a
added unit tests for item create/edit/delete
MatteoGuarnaccia5 Oct 7, 2025
a79dc78
added functionality to allow admins to select usage status when movin…
MatteoGuarnaccia5 Oct 8, 2025
e785da0
change admin warnings to tooltips in dialogs
MatteoGuarnaccia5 Oct 14, 2025
d6ec196
changed isUserAuthorised variable to isAdminUser
MatteoGuarnaccia5 Oct 14, 2025
07ae599
added and refactored unit tests
MatteoGuarnaccia5 Oct 14, 2025
f58ec51
update snapshot
MatteoGuarnaccia5 Oct 15, 2025
336f513
added e2e tests
MatteoGuarnaccia5 Oct 15, 2025
56b1713
removed it.only
MatteoGuarnaccia5 Oct 15, 2025
0e6aa22
Merge branch 'refactor-admin-page-to-utilise-authorisation-#1472' int…
MatteoGuarnaccia5 Oct 20, 2025
b743e4d
update tokens to reflect changes in ldap-jwt-auth
MatteoGuarnaccia5 Oct 20, 2025
08423d8
update snapshots
MatteoGuarnaccia5 Oct 20, 2025
7b9282e
Merge branch 'refactor-admin-page-to-utilise-authorisation-#1472' int…
MatteoGuarnaccia5 Oct 22, 2025
4917123
fix missing isAdminUser to isPrivilegedUser
MatteoGuarnaccia5 Oct 22, 2025
a50bf4d
Merge branch 'refactor-admin-page-to-utilise-authorisation-#1472' int…
MatteoGuarnaccia5 Oct 30, 2025
05e47f9
update failing snapshots
MatteoGuarnaccia5 Nov 3, 2025
8f01d95
refactor admin tooltips, usage status logic in system item dialog, an…
MatteoGuarnaccia5 Nov 3, 2025
a94a358
Merge branch 'refactor-admin-page-to-utilise-authorisation-#1472' int…
MatteoGuarnaccia5 Nov 5, 2025
25624cb
address comments regarding tooltips and button capitalisation
MatteoGuarnaccia5 Nov 10, 2025
99422d1
refactor view of action menus and add duplicate as admin
MatteoGuarnaccia5 Nov 10, 2025
c66e502
update comment
MatteoGuarnaccia5 Nov 10, 2025
ea485f0
change to not prefilling usage statuses on no-rule moves
MatteoGuarnaccia5 Nov 10, 2025
42f4d4a
fix item dialog usage statuses
MatteoGuarnaccia5 Nov 10, 2025
acae189
fix unit tests and controlled element react error
MatteoGuarnaccia5 Nov 10, 2025
829ecef
Merge branch 'refactor-admin-page-to-utilise-authorisation-#1472' int…
MatteoGuarnaccia5 Nov 11, 2025
7516ea7
expand usage status table by default
MatteoGuarnaccia5 Nov 11, 2025
43fc561
fix unit tests
MatteoGuarnaccia5 Nov 11, 2025
daa53e5
fix e2e test
MatteoGuarnaccia5 Nov 11, 2025
84afb08
increase test timeout
MatteoGuarnaccia5 Nov 11, 2025
543c00c
Merge branch 'refactor-admin-page-to-utilise-authorisation-#1472' int…
MatteoGuarnaccia5 Nov 11, 2025
f238012
fix linting
MatteoGuarnaccia5 Nov 11, 2025
5bbdbbc
Merge branch 'refactor-admin-page-to-utilise-authorisation-#1472' int…
MatteoGuarnaccia5 Nov 11, 2025
edbb5f2
update snapshot and remove console.log
MatteoGuarnaccia5 Nov 11, 2025
656d576
prepopulated usage statuses and removed undeeded error handling
MatteoGuarnaccia5 Nov 12, 2025
b991d19
in item dialog default to current usage status when editing
MatteoGuarnaccia5 Nov 12, 2025
f1a46fe
make tooltips follow convention of others in ims
MatteoGuarnaccia5 Nov 12, 2025
e04ac84
Merge branch 'implement-base-authorisation-#1472' into implement-auth…
MatteoGuarnaccia5 Nov 12, 2025
1e5a5db
refactor snapshot test to use baseElement
MatteoGuarnaccia5 Nov 13, 2025
86ace83
Merge branch 'implement-base-authorisation-#1472' into implement-auth…
MatteoGuarnaccia5 Nov 13, 2025
5c9adef
update snapshots
MatteoGuarnaccia5 Nov 13, 2025
c3a3553
fix snapshot tests
MatteoGuarnaccia5 Nov 13, 2025
273aca3
add waitfor to stop progress indicator being in snapshot
MatteoGuarnaccia5 Nov 13, 2025
9a5ae75
remove not required comments
MatteoGuarnaccia5 Nov 13, 2025
c741d78
update snapshot
MatteoGuarnaccia5 Nov 13, 2025
85cc31d
refactor systemItemsDialog logic in regards to usage statuses to use …
MatteoGuarnaccia5 Nov 17, 2025
123f479
renamed test
MatteoGuarnaccia5 Nov 17, 2025
3add236
update snapshot
MatteoGuarnaccia5 Nov 17, 2025
2968c1b
update snapshot #1523
joshdimanteto Nov 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 123 additions & 1 deletion cypress/e2e/with_mock_data/items.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,62 @@ describe('Items', () => {
});
});

it('displays add dialog in admin view when user is admin', () => {
cy.setCurrentUserToAdmin();
cy.visit('/catalogue/4/items/1/items');

cy.findByRole('button', { name: 'Add Item as Admin' }).click();

cy.findByRole('progressbar').should('not.exist');

// Operational is 'not allowed' by rules so admin user should be able to bypass this and select usasge status
cy.findAllByText('Operational').first().click();

// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(100);

cy.findByRole('button', { name: 'Next' }).click();

cy.findByLabelText('Usage status *').click();
cy.findByRole('option', { name: 'In Use' }).click();

cy.startSnoopingBrowserMockedRequest();

cy.findByRole('button', { name: 'Next' }).click();

cy.findByRole('button', { name: 'Finish' }).click();
cy.findByRole('dialog').should('not.exist');

cy.findBrowserMockedRequests({
method: 'POST',
url: '/v1/items',
}).should(async (postRequests) => {
expect(postRequests.length).eq(1);
expect(JSON.stringify(await postRequests[0].json())).equal(
JSON.stringify({
purchase_order_number: null,
is_defective: false,
usage_status_id: '1',
warranty_end_date: null,
asset_number: null,
serial_number: null,
delivered_date: null,
notes: null,
properties: [
{ id: '1', value: 12 },
{ id: '2', value: 30 },
{ id: '3', value: 'CMOS' },
{ id: '4', value: null },
{ id: '5', value: true },
{ id: '6', value: false },
],
catalogue_item_id: '1',
system_id: '65328f34a40ff5301575a4e3',
})
);
});
});

it('displays messages for incorrect input types', () => {
cy.findByRole('button', { name: 'Add Item' }).click();

Expand Down Expand Up @@ -1775,7 +1831,49 @@ describe('Items', () => {
});
});

it('editing an item should display an error message if values have not been updated', () => {
it('displays edit dialog in admin view when user is admin', () => {
cy.setCurrentUserToAdmin();
cy.visit('/catalogue/9/items/11/items');
cy.findAllByLabelText('Row Actions').first().click();
cy.findByText('Edit as Admin').click();

cy.findByRole('progressbar').should('not.exist');
cy.findByText('Edit Item as Admin').should('exist');

// Scrapped is 'not allowed' by rules so admin user should be able to bypass this and override usasge status
cy.findByRole('button', { name: 'navigate to systems home' }).click();
cy.findAllByText('Scrapped').first().click();

// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(100);

cy.findByRole('button', { name: 'Next' }).click();

cy.findByLabelText('Usage status *').click();
cy.findByRole('option', { name: 'Used' }).click();

cy.startSnoopingBrowserMockedRequest();

cy.findByRole('button', { name: 'Next' }).click();

cy.findByRole('button', { name: 'Finish' }).click();
cy.findByRole('dialog').should('not.exist');

cy.findBrowserMockedRequests({
method: 'PATCH',
url: '/v1/items/:id',
}).should(async (patchRequests) => {
expect(patchRequests.length).eq(1);
expect(JSON.stringify(await patchRequests[0].json())).equal(
JSON.stringify({
usage_status_id: '2',
system_id: '657f8c3b2a1b4e5d8f9b3c4e8',
})
);
});
});

it('should display an error message if values have not been updated', () => {
cy.findAllByLabelText('Row Actions').last().click();
cy.findByText('Edit').click();

Expand Down Expand Up @@ -1889,4 +1987,28 @@ describe('Items', () => {
'Please move item to a system with Type: Storage before trying to delete.'
).should('exist');
});

it('admin user can bypass delete rules and delete dialog is in admin view', () => {
cy.setCurrentUserToAdmin();
cy.visit('/catalogue/4/items/1/items');

cy.findAllByLabelText('Row Actions').first().click();
cy.findByText('Delete as Admin').click();

cy.findByText('Serial Number: 5YUQDDjKpz2z').should('exist');
cy.findByText('Delete Item as Admin').should('exist');

cy.startSnoopingBrowserMockedRequest();

cy.findByRole('button', { name: 'Continue' }).click();

cy.findBrowserMockedRequests({
method: 'DELETE',
url: '/v1/items/:id',
}).should((deleteRequests) => {
expect(deleteRequests.length).equal(1);
const request = deleteRequests[0];
expect(request.url.toString()).to.contain('KvT2Ox7n');
});
});
});
74 changes: 74 additions & 0 deletions cypress/e2e/with_mock_data/systems.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,80 @@ describe('Systems', () => {
});
});

it('moves items (admin mode)', () => {
cy.setCurrentUserToAdmin();
cy.visit('/systems');

cy.findByRole('link', { name: 'Pulse Laser' }).click();
cy.findAllByRole('button', { name: 'Show/Hide filters' })
.eq(1)
.scrollIntoView();
cy.findAllByRole('button', { name: 'Show/Hide filters' }).eq(1).click();

// Wait for progress bar to disappear before interacting with filters
cy.findAllByRole('progressbar').should('not.exist');

cy.findAllByRole('button', { name: 'Expand' }).eq(1).scrollIntoView();
cy.findAllByRole('button', { name: 'Expand' }).eq(1).click();

// Second table, first checkbox
cy.findAllByRole('table')
.eq(1)
.within(() => {
cy.findAllByRole('checkbox', {
name: 'Toggle select row',
})
.eq(1)
.click({ force: true });

cy.findAllByRole('checkbox', {
name: 'Toggle select row',
})
.eq(2)
.click({ force: true });
});

cy.findByRole('button', { name: 'Move to as Admin' }).click();

cy.startSnoopingBrowserMockedRequest();

cy.findByRole('dialog')
.should('be.visible')
.within(() => {
cy.findByRole('button', { name: 'navigate to systems home' }).click();
cy.findByLabelText('Giant laser row').click();
cy.findByRole('button', { name: 'Next' }).click();
});

cy.findAllByRole('combobox').eq(1).click();
cy.findByRole('option', { name: 'Scrapped' }).click();

cy.findByRole('button', { name: 'Finish' }).click();

cy.findByRole('dialog').should('not.exist');

cy.findBrowserMockedRequests({
method: 'PATCH',
url: '/v1/items/:id',
}).should(async (patchRequests) => {
expect(patchRequests.length).eq(2);
expect(patchRequests[0].url.toString()).to.contain('/z1hJvV8Z');
expect(JSON.stringify(await patchRequests[0].json())).equal(
JSON.stringify({
system_id: '65328f34a40ff5301575a4e3',
usage_status_id: '3',
})
);
expect(patchRequests[1].url.toString()).to.contain('/4mYoI7pr');
expect(JSON.stringify(await patchRequests[1].json())).equal(
JSON.stringify({
system_id: '65328f34a40ff5301575a4e3',
usage_status_id: '3',
})
);
});
});

it('display errors message and clears error message when resolved', () => {
cy.findByRole('link', { name: 'Pulse Laser' }).click();
cy.findAllByRole('button', { name: 'Show/Hide filters' })
Expand Down
49 changes: 43 additions & 6 deletions src/api/items.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { renderHook, waitFor } from '@testing-library/react';
import { MockInstance } from 'vitest';
import { MoveItemsToSystem, PostItems } from '../app.types';
import {
MoveItemsToSystem,
MoveItemsToSystemUsageStatus,
PostItems,
} from '../app.types';
import SystemsJSON from '../mocks/Systems.json';
import {
getItemById,
Expand Down Expand Up @@ -242,7 +246,7 @@ describe('items api functions', () => {
beforeEach(() => {
moveItemsToSystem = {
// Prevent test interference if modifying the usage statuses or selected items
usageStatusId: '0',
usageStatusConfig: '0',
selectedItems: JSON.parse(JSON.stringify(mockItems)),
targetSystem: SystemsJSON[1] as System,
};
Expand All @@ -254,7 +258,38 @@ describe('items api functions', () => {
vi.clearAllMocks();
});

it('sends requests to move multiple items to a system and returns a successful response for each', async () => {
it('sends requests to move multiple items to a system and returns a successful response for each (config as string)', async () => {
const { result } = renderHook(() => useMoveItemsToSystem(), {
wrapper: hooksWrapperWithProviders(),
});

expect(result.current.isIdle).toBe(true);

result.current.mutate(moveItemsToSystem);

await waitFor(() => {
expect(result.current.isSuccess).toBeTruthy();
});
moveItemsToSystem.selectedItems.map((item) =>
expect(axiosPatchSpy).toHaveBeenCalledWith(`/v1/items/${item.id}`, {
system_id: moveItemsToSystem.targetSystem.id,
usage_status_id: moveItemsToSystem.usageStatusConfig,
})
);
expect(result.current.data).toEqual(
moveItemsToSystem.selectedItems.map((item) => ({
message: `Successfully moved to Giant laser`,
name: item.serial_number,
state: 'success',
}))
);
});

it('sends requests to move multiple items to a system and returns a successful response for each (config as list)', async () => {
moveItemsToSystem.usageStatusConfig = [
{ item_id: 'KvT2Ox7n', usage_status_id: '0' },
{ item_id: 'G463gOIA', usage_status_id: '0' },
];
const { result } = renderHook(() => useMoveItemsToSystem(), {
wrapper: hooksWrapperWithProviders(),
});
Expand All @@ -269,7 +304,9 @@ describe('items api functions', () => {
moveItemsToSystem.selectedItems.map((item) =>
expect(axiosPatchSpy).toHaveBeenCalledWith(`/v1/items/${item.id}`, {
system_id: moveItemsToSystem.targetSystem.id,
usage_status_id: moveItemsToSystem.usageStatusId,
usage_status_id: (
moveItemsToSystem.usageStatusConfig as Array<MoveItemsToSystemUsageStatus>
).find((status) => status.item_id === item.id)?.usage_status_id,
})
);
expect(result.current.data).toEqual(
Expand All @@ -287,7 +324,7 @@ describe('items api functions', () => {
name: 'New system name',
id: 'new_system_id',
};
moveItemsToSystem.usageStatusId = '2';
moveItemsToSystem.usageStatusConfig = '2';

// Fail just the 1st system
moveItemsToSystem.selectedItems[0].id = 'Error 409';
Expand All @@ -307,7 +344,7 @@ describe('items api functions', () => {
moveItemsToSystem.selectedItems.map((item) =>
expect(axiosPatchSpy).toHaveBeenCalledWith(`/v1/items/${item.id}`, {
system_id: 'new_system_id',
usage_status_id: moveItemsToSystem.usageStatusId,
usage_status_id: moveItemsToSystem.usageStatusConfig,
})
);
expect(result.current.data).toEqual(
Expand Down
12 changes: 10 additions & 2 deletions src/api/items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,18 @@ export const useMoveItemsToSystem = (): UseMutationResult<

const promises = moveItemsToSystem.selectedItems.map(
async (item: Item) => {
const usage_status_id: string | undefined = Array.isArray(
moveItemsToSystem.usageStatusConfig
)
? moveItemsToSystem.usageStatusConfig.find(
(status) => status.item_id === item.id
)?.usage_status_id
: moveItemsToSystem.usageStatusConfig;

return patchItem(item.id, {
system_id: moveItemsToSystem.targetSystem?.id || '',
...(moveItemsToSystem.usageStatusId && {
usage_status_id: moveItemsToSystem.usageStatusId,
...(usage_status_id && {
usage_status_id: usage_status_id,
}),
})
.then((result: Item) => {
Expand Down
6 changes: 5 additions & 1 deletion src/app.types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,12 @@ export interface PostItems {
item: ItemPost;
}

export interface MoveItemsToSystemUsageStatus {
item_id: string;
usage_status_id: string;
}
export interface MoveItemsToSystem {
usageStatusId?: string;
usageStatusConfig?: MoveItemsToSystemUsageStatus[] | string;
selectedItems: Item[];
targetSystem: System;
}
Expand Down
4 changes: 4 additions & 0 deletions src/common/actionMenu.component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('ActionMenu Component', () => {
uploadImagesEntityId: '1',
editMenuItem: mockEditMenuItem,
printMenuItem: true,
showAdminEdit: true, // true to check it renders the option correctly
};
user = userEvent.setup();
// Mock the window.print function
Expand Down Expand Up @@ -51,6 +52,9 @@ describe('ActionMenu Component', () => {
// Check if the "Edit" option is visible
expect(screen.getByText('Edit')).toBeVisible();

// Check if the "Edit as Admin" option is visible
expect(screen.getByText('Edit as Admin')).toBeVisible();

// Check if the "Print" option is visible
expect(screen.getByText('Print')).toBeVisible();
});
Expand Down
Loading