Skip to content

Commit

Permalink
feat: show delete item login schema alert to permanently remove data (#…
Browse files Browse the repository at this point in the history
…1577)

* feat: show delete item login schema alert to permanently remove data

* test: add tests

* refactor: fix test

* refactor: move up delete item login schema button

* refactor: apply PR requested changes
  • Loading branch information
pyphilia authored Nov 28, 2024
1 parent bfe4205 commit 71e776b
Show file tree
Hide file tree
Showing 12 changed files with 363 additions and 17 deletions.
121 changes: 120 additions & 1 deletion cypress/e2e/item/authorization/itemLogin/itemLoginSetting.cy.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import {
GuestFactory,
ItemLoginSchemaStatus,
ItemLoginSchemaType,
PackedFolderItemFactory,
PermissionLevel,
} from '@graasp/sdk';

import { buildItemPath } from '../../../../../src/config/paths';
import {
buildItemPath,
buildItemSharePath,
} from '../../../../../src/config/paths';
import {
REQUEST_MEMBERSHIP_BUTTON_ID,
SHARE_ITEM_PSEUDONYMIZED_SCHEMA_ID,
buildDataCyWrapper,
buildShareButtonId,
} from '../../../../../src/config/selectors';
import { MEMBERS } from '../../../../fixtures/members';
import { buildItemMembership } from '../../../../fixtures/memberships';
import { ITEM_LOGIN_PAUSE } from '../../../../support/constants';
import { addItemLoginSchema } from './utils';

const ALERT_BUTTON = `[role="alert"] button`;
const DIALOG_SELECTOR = `[role="dialog"]`;

const checkItemLoginSetting = ({
mode,
disabled = false,
Expand Down Expand Up @@ -98,3 +108,112 @@ describe('Item Login', () => {
});
});
});

describe('Item Login Delete Button', () => {
describe('without guests', () => {
it('Delete item login for private item ', () => {
const item = addItemLoginSchema(
PackedFolderItemFactory({}),
ItemLoginSchemaType.UsernameAndPassword,
ItemLoginSchemaStatus.Disabled,
);
cy.setUpApi({
items: [item],
});
cy.visit(buildItemSharePath(item.id));

// delete
cy.get(ALERT_BUTTON).click();
cy.wait('@deleteItemLoginSchema').then(({ request: { url } }) => {
expect(url).to.include(item.id);
});
});

it('Delete item login for public item', () => {
const item = addItemLoginSchema(
PackedFolderItemFactory({}, { publicVisibility: {} }),
ItemLoginSchemaType.UsernameAndPassword,
ItemLoginSchemaStatus.Disabled,
);
cy.setUpApi({
items: [item],
});
cy.visit(buildItemSharePath(item.id));

// delete
cy.get(ALERT_BUTTON).click();
cy.wait('@deleteItemLoginSchema').then(({ request: { url } }) => {
expect(url).to.include(item.id);
});
});
});
describe('with guests', () => {
it('Delete item login for private item ', () => {
const item = addItemLoginSchema(
PackedFolderItemFactory({}),
ItemLoginSchemaType.UsernameAndPassword,
ItemLoginSchemaStatus.Disabled,
);
const guest = GuestFactory({ itemLoginSchema: item.itemLoginSchema });
cy.setUpApi({
items: [
{
...item,
memberships: [
buildItemMembership({
item,
account: guest,
permission: PermissionLevel.Read,
}),
],
},
],
});
cy.visit(buildItemSharePath(item.id));

// display delete alert
cy.get(ALERT_BUTTON).click();
cy.get(DIALOG_SELECTOR).should('contain', guest.name);

// click delete
cy.get(`${DIALOG_SELECTOR} ${buildDataCyWrapper('delete')}`).click();
cy.wait('@deleteItemLoginSchema').then(({ request: { url } }) => {
expect(url).to.include(item.id);
});
});

it('Delete item login for public item', () => {
const item = addItemLoginSchema(
PackedFolderItemFactory({}, { publicVisibility: {} }),
ItemLoginSchemaType.UsernameAndPassword,
ItemLoginSchemaStatus.Disabled,
);
const guest = GuestFactory({ itemLoginSchema: item.itemLoginSchema });
cy.setUpApi({
items: [
{
...item,
memberships: [
buildItemMembership({
item,
account: guest,
permission: PermissionLevel.Read,
}),
],
},
],
});
cy.visit(buildItemSharePath(item.id));

// display delete alert
cy.get(ALERT_BUTTON).click();
cy.get(DIALOG_SELECTOR).should('contain', guest.name);

// click delete
cy.get(`${DIALOG_SELECTOR} ${buildDataCyWrapper('delete')}`).click();
cy.wait('@deleteItemLoginSchema').then(({ request: { url } }) => {
expect(url).to.include(item.id);
});
});
});
});
3 changes: 2 additions & 1 deletion cypress/e2e/item/authorization/itemLogin/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import {
export const addItemLoginSchema = (
item: PackedItem,
itemLoginSchemaType: ItemLoginSchemaType,
status = ItemLoginSchemaStatus.Active,
): PackedItem & { itemLoginSchema: ItemLoginSchema } => ({
...item,
itemLoginSchema: ItemLoginSchemaFactory({
item,
type: itemLoginSchemaType,
status: ItemLoginSchemaStatus.Active,
status,
}),
});
54 changes: 51 additions & 3 deletions cypress/e2e/item/share/changeVisibility.cy.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
GuestFactory,
ItemLoginSchemaStatus,
ItemLoginSchemaType,
ItemVisibilityType,
PackedFolderItemFactory,
PermissionLevel,
PublicationStatus,
} from '@graasp/sdk';

Expand All @@ -18,6 +20,8 @@ import {
buildShareButtonId,
} from '../../../../src/config/selectors';
import { PublishedItemFactory } from '../../../fixtures/items';
import { buildItemMembership } from '../../../fixtures/memberships';
import { addItemLoginSchema } from '../authorization/itemLogin/utils';

const changeVisibility = (value: string): void => {
cy.get(`#${SHARE_ITEM_VISIBILITY_SELECT_ID}`).click();
Expand Down Expand Up @@ -102,7 +106,51 @@ describe('Visibility of an Item', () => {
});
});

it('Change Pseudonymized Item to Private Item', () => {
it('Change Pseudonymized Item to Private Item with guest', () => {
const item = addItemLoginSchema(
PackedFolderItemFactory({}),
ItemLoginSchemaType.Username,
);
const guest = GuestFactory({ itemLoginSchema: item.itemLoginSchema });
cy.setUpApi({
items: [
{
...item,
memberships: [
buildItemMembership({
item,
account: guest,
permission: PermissionLevel.Read,
}),
],
},
],
});
cy.visit(buildItemPath(item.id));
cy.get(`#${buildShareButtonId(item.id)}`).click();

// visibility select default value
cy.get(`#${SHARE_ITEM_VISIBILITY_SELECT_ID} + input`).should(
'have.value',
SETTINGS.ITEM_LOGIN.name,
);

// change item login schema
cy.get(`#${SHARE_ITEM_PSEUDONYMIZED_SCHEMA_ID} + input`).should(
'have.value',
ItemLoginSchemaType.Username,
);
// item login edition is done in itemLogin.cy.js

// change pseudonymized -> private
changeVisibility(SETTINGS.ITEM_PRIVATE.name);
cy.wait(`@putItemLoginSchema`).then(({ request: { url, body } }) => {
expect(url).to.include(item.id);
expect(body.status).to.eq(ItemLoginSchemaStatus.Disabled);
});
});

it('Change Pseudonymized Item to Private Item without guest', () => {
const item = PackedFolderItemFactory();
const ITEM_LOGIN_ITEM = {
...item,
Expand Down Expand Up @@ -132,10 +180,10 @@ describe('Visibility of an Item', () => {
// item login edition is done in itemLogin.cy.js

// change pseudonymized -> private
// delete item login
changeVisibility(SETTINGS.ITEM_PRIVATE.name);
cy.wait(`@putItemLoginSchema`).then(({ request: { url, body } }) => {
cy.wait(`@deleteItemLoginSchema`).then(({ request: { url } }) => {
expect(url).to.include(item.id);
expect(body.status).to.eq(ItemLoginSchemaStatus.Disabled);
});
});

Expand Down
3 changes: 3 additions & 0 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
mockDeleteFavorite,
mockDeleteInvitation,
mockDeleteItemCategory,
mockDeleteItemLoginSchema,
mockDeleteItemMembershipForItem,
mockDeleteItemThumbnail,
mockDeleteItemVisibility,
Expand Down Expand Up @@ -226,6 +227,8 @@ Cypress.Commands.add(

mockPutItemLoginSchema(cachedItems, putItemLoginError);

mockDeleteItemLoginSchema();

mockGetItemMembershipsForItem(items, currentMember);

mockPostItemVisibility(cachedItems, currentMember, postItemVisibilityError);
Expand Down
15 changes: 15 additions & 0 deletions cypress/support/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,21 @@ export const mockPutItemLoginSchema = (
).as('putItemLoginSchema');
};

export const mockDeleteItemLoginSchema = (): void => {
cy.intercept(
{
method: HttpMethod.Delete,
url: new RegExp(`${API_HOST}/items/${ID_FORMAT}/login-schema$`),
},
({ reply, url }) => {
// check query match item login schema
const id = url.slice(API_HOST.length).split('/')[2];

reply(id);
},
).as('deleteItemLoginSchema');
};

export const mockGetItemLogin = (items: ItemForTest[]): void => {
cy.intercept(
{
Expand Down
16 changes: 16 additions & 0 deletions src/components/hooks/useGuestMemberships.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AccountType, DiscriminatedItem, ItemMembership } from '@graasp/sdk';

import { hooks } from '@/config/queryClient';

export function useGuestMemberships(itemId: DiscriminatedItem['id']): {
isLoading: boolean;
data?: ItemMembership[];
} {
const { data: memberships, isLoading } = hooks.useItemMemberships(itemId);

const guestMemberships = memberships?.filter(
({ account: { type } }) => type === AccountType.Guest,
);

return { isLoading, data: guestMemberships };
}
27 changes: 21 additions & 6 deletions src/components/hooks/useVisibility.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
import { SETTINGS } from '@/config/constants';
import { hooks, mutations } from '@/config/queryClient';

import { useGuestMemberships } from './useGuestMemberships';

const { useItemLoginSchema, useItemPublishedInformation } = hooks;
const {
useDeleteItemVisibility,
Expand All @@ -34,6 +36,11 @@ type UseVisibility = {
export const useVisibility = (item: PackedItem): UseVisibility => {
const { mutateAsync: postItemVisibility } = usePostItemVisibility();
const { mutate: deleteItemVisibility } = useDeleteItemVisibility();
const { mutate: deleteItemLoginSchema } =
mutations.useDeleteItemLoginSchema();

const { data: guestMemberships, isLoading: isGuestNbLoading } =
useGuestMemberships(item.id);

// get item published
const { data: itemPublishEntry, isLoading: isItemPublishEntryLoading } =
Expand Down Expand Up @@ -65,7 +72,8 @@ export const useVisibility = (item: PackedItem): UseVisibility => {
}, [itemLoginSchema, item]);

// is loading
const isLoading = isItemPublishEntryLoading || isItemLoginLoading;
const isLoading =
isItemPublishEntryLoading || isItemLoginLoading || isGuestNbLoading;

// visibility
const [visibility, setVisibility] = useState<string>(
Expand Down Expand Up @@ -106,11 +114,16 @@ export const useVisibility = (item: PackedItem): UseVisibility => {
};

const disableLoginSchema = () => {
if (itemLoginSchema) {
putItemLoginSchema({
itemId: item.id,
status: ItemLoginSchemaStatus.Disabled,
});
if (itemLoginSchema && guestMemberships) {
// disable if contains data, delete otherwise
if (guestMemberships.length) {
putItemLoginSchema({
itemId: item.id,
status: ItemLoginSchemaStatus.Disabled,
});
} else {
deleteItemLoginSchema({ itemId: item.id });
}
}
};

Expand Down Expand Up @@ -148,7 +161,9 @@ export const useVisibility = (item: PackedItem): UseVisibility => {
unpublish,
deleteItemVisibility,
itemLoginSchema,
guestMemberships,
putItemLoginSchema,
deleteItemLoginSchema,
postItemVisibility,
],
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/item/form/link/LinkForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export const LinkForm = ({
<DialogContent>
<Stack gap={1} overflow="scroll">
<LinkUrlField />
<ItemNameField required />
<ItemNameField required autoFocus={false} />
<LinkDescriptionField
onRestore={() =>
setValue('description', linkData?.description ?? '')
Expand Down
Loading

0 comments on commit 71e776b

Please sign in to comment.