Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MS] Added revoked date in user details modal #8862

Merged
merged 3 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion client/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,8 @@
"name": "Name",
"joined": "Joined",
"commonWorkspaces": "Shared workspaces",
"revoked": "Revoked"
"revoked": "Revoked",
"revokedSince": "Revoked since"
},
"actions": {
"close": "Close"
Expand Down
3 changes: 2 additions & 1 deletion client/src/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,8 @@
"name": "Nom",
"joined": "Rejoint",
"commonWorkspaces": "Espaces de travail communs",
"revoked": "Révoqué"
"revoked": "Révoqué",
"revokedSince": "Révoqué depuis le"
},
"actions": {
"close": "Fermer"
Expand Down
8 changes: 4 additions & 4 deletions client/src/parsec/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export async function listUsers(skipRevoked = true, pattern = ''): Promise<Resul
currentProfile: UserProfile.Admin,
createdOn: DateTime.utc(2002, 7, 3),
createdBy: 'device',
revokedOn: DateTime.now(),
revokedOn: DateTime.utc(2022, 4, 7),
revokedBy: 'device',
isRevoked: (): boolean => true,
isFrozen: (): boolean => false,
Expand All @@ -121,7 +121,7 @@ export async function listUsers(skipRevoked = true, pattern = ''): Promise<Resul
currentProfile: UserProfile.Outsider,
createdOn: DateTime.utc(2019, 7, 16),
createdBy: 'device',
revokedOn: DateTime.now(),
revokedOn: DateTime.utc(2023, 11, 3),
revokedBy: 'device',
isRevoked: (): boolean => true,
isFrozen: (): boolean => false,
Expand All @@ -131,9 +131,9 @@ export async function listUsers(skipRevoked = true, pattern = ''): Promise<Resul
// cspell:disable-next-line
humanHandle: { label: 'Valygar Corthala', email: '[email protected]' },
currentProfile: UserProfile.Standard,
createdOn: DateTime.now(),
createdOn: DateTime.utc(2015, 2, 17),
createdBy: 'device',
revokedOn: DateTime.now(),
revokedOn: DateTime.utc(2024, 5, 18),
revokedBy: 'device',
isRevoked: (): boolean => true,
isFrozen: (): boolean => false,
Expand Down
111 changes: 86 additions & 25 deletions client/src/views/users/UserDetailsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,59 @@
}"
>
<div class="details">
<div class="details-item">
<ion-text class="details-item__title subtitles-sm">
{{ $msTranslate('UsersPage.UserDetailsModal.subtitles.name') }}
</ion-text>
<ion-text class="details-item__text subtitles-normal">
{{ user.humanHandle.label }}
</ion-text>
<div
class="details-item"
:class="{ revoked: user.isRevoked() }"
>
<div class="details-item-name">
<ion-text class="details-item-name__title subtitles-sm">
{{ $msTranslate('UsersPage.UserDetailsModal.subtitles.name') }}
</ion-text>
<ion-text class="details-item-name__text subtitles-normal">
{{ user.humanHandle.label }}
</ion-text>
</div>
<ion-chip
v-if="user.isRevoked()"
color="danger"
class="revoked"
class="revoked-chip"
>
<ion-icon
class="revoked__icon"
:icon="ellipse"
/>
<ion-label class="subtitles-sm">
{{ $msTranslate('UsersPage.UserDetailsModal.subtitles.revoked') }}
</ion-label>
</ion-chip>
</div>
<div class="details-item">
<ion-text class="details-item__title subtitles-sm">

<!-- join on -->
<div class="details-item time-item">
<ion-text class="details-item-name__title subtitles-sm">
<ion-icon
class="details-item__icon"
:icon="personAdd"
/>
{{ $msTranslate('UsersPage.UserDetailsModal.subtitles.joined') }}
</ion-text>
<ion-text class="details-item__text body-lg">
<ion-text class="details-item-name__text body-lg">
{{ $msTranslate(formatTimeSince(user.createdOn, '--', 'short')) }}
</ion-text>
</div>

<!-- revoked since -->
<div
class="details-item time-item"
v-if="user.isRevoked() && user.revokedOn"
>
<ion-text class="details-item-name__title">
<ion-icon
class="details-item__icon body-lg"
:icon="personRemove"
/>
<span class="subtitles-sm">{{ $msTranslate('UsersPage.UserDetailsModal.subtitles.revokedSince') }}</span>
</ion-text>
<ion-text class="details-item-name__text body-lg">
{{ $msTranslate(I18n.formatDate(user.revokedOn, 'short')) }}
</ion-text>
</div>
</div>

<ion-list class="workspace">
Expand Down Expand Up @@ -83,12 +107,12 @@
</template>

<script setup lang="ts">
import { MsModal, formatTimeSince } from 'megashark-lib';
import { MsModal, formatTimeSince, I18n } from 'megashark-lib';
import WorkspaceTagRole from '@/components/workspaces/WorkspaceTagRole.vue';
import { SharedWithInfo, UserInfo, getWorkspacesSharedWith } from '@/parsec';
import { Information, InformationLevel, InformationManager, PresentationMode } from '@/services/informationManager';
import { IonCard, IonCardContent, IonChip, IonIcon, IonLabel, IonList, IonPage, IonText } from '@ionic/vue';
import { business, ellipse } from 'ionicons/icons';
import { business, personAdd, personRemove } from 'ionicons/icons';
import { Ref, onMounted, ref } from 'vue';

const sharedWorkspaces: Ref<Array<SharedWithInfo>> = ref([]);
Expand Down Expand Up @@ -119,34 +143,71 @@ onMounted(async () => {
.details {
display: flex;
margin-bottom: 1rem;
flex-wrap: wrap;
gap: 1rem;

.details-item {
display: flex;
flex-direction: column;
width: 50%;
flex-shrink: 0;
width: calc(50% - 0.5rem);
gap: 0.5rem;

&__title {
color: var(--parsec-color-light-secondary-text);
&.revoked {
width: 100%;
flex-direction: row;
gap: 1rem;
margin-bottom: 1rem;
}

&__text {
color: var(--parsec-color-light-primary-800);
&-name {
margin: auto 0;
display: flex;
flex-direction: column;
gap: 0.5rem;

&__title {
color: var(--parsec-color-light-secondary-text);
}

&__text {
color: var(--parsec-color-light-primary-800);
}
}

.revoked {
.revoked-chip {
display: flex;
gap: 0.5rem;
align-items: center;
align-self: end;
justify-content: center;
width: 5.5rem;
padding: 0.125rem;
min-height: 0;
border-radius: var(--parsec-radius-6);
margin: 0;
height: fit-content;
}

&.time-item {
background: var(--parsec-color-light-secondary-background);
padding: 0.75rem;
border: 1px solid var(--parsec-color-light-secondary-premiere);
border-radius: var(--parsec-radius-6);

.details-item__icon {
color: var(--parsec-color-light-secondary-hard-grey);
}

.details-item-name__title {
color: var(--parsec-color-light-secondary-hard-grey);
display: flex;
align-items: center;
gap: 0.5rem;
}

&__icon {
font-size: 0.375rem;
.details-item-name__text {
color: var(--parsec-color-light-primary-800);
}
}
}
Expand Down
28 changes: 18 additions & 10 deletions client/tests/pw/e2e/user_details.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import { msTest } from '@tests/pw/helpers/fixtures';
async function openModalWithUser(usersPage: Page, userIndex: number): Promise<Locator> {
await expect(usersPage.locator('.user-details-modal')).toBeHidden();
const user = usersPage.locator('#users-page-user-list').getByRole('listitem').nth(userIndex);
const isRevoked = (await user.locator('.user-status').innerText()) === 'Revoked';
await user.hover();
await user.locator('.options-button').click();
const popover = usersPage.locator('.user-context-menu');
await popover.getByRole('listitem').nth(3).click();
await popover
.getByRole('listitem')
.nth(isRevoked ? 1 : 3)
.click();
const modal = usersPage.locator('.user-details-modal');
await expect(modal).toBeVisible();
return modal;
Expand All @@ -21,11 +25,11 @@ msTest('User details modal', async ({ usersPage }) => {

await expect(modal.locator('.ms-modal-header__title')).toHaveText('User details');
const detailsItems = modal.locator('.ms-modal-content').locator('.details-item');
await expect(detailsItems.nth(0).locator('.details-item__title')).toHaveText('Name');
await expect(detailsItems.nth(0).locator('.details-item-name__title')).toHaveText('Name');
// cspell:disable-next-line
await expect(detailsItems.nth(0).locator('.details-item__text')).toHaveText('Cernd');
await expect(detailsItems.nth(1).locator('.details-item__title')).toHaveText('Joined');
await expect(detailsItems.nth(1).locator('.details-item__text')).toHaveText(/^now|< 1 minute$/);
await expect(detailsItems.nth(0).locator('.details-item-name__text')).toHaveText('Cernd');
await expect(detailsItems.nth(1).locator('.details-item-name__title')).toHaveText('Joined');
await expect(detailsItems.nth(1).locator('.details-item-name__text')).toHaveText(/^now|< 1 minute$/);
await expect(modal.locator('.workspace-list')).toBeVisible();
await expect(modal.locator('.workspace-empty')).toBeHidden();
await expect(modal.locator('.workspace-list').locator('.workspace-list-item').locator('.item-container__name')).toHaveText([
Expand All @@ -36,14 +40,18 @@ msTest('User details modal', async ({ usersPage }) => {
});

msTest('User details modal no common workspaces', async ({ usersPage }) => {
const modal = await openModalWithUser(usersPage, 4);
const modal = await openModalWithUser(usersPage, 2);

await expect(modal.locator('.ms-modal-header__title')).toHaveText('User details');
const detailsItems = modal.locator('.ms-modal-content').locator('.details-item');
await expect(detailsItems.nth(0).locator('.details-item__title')).toHaveText('Name');
await expect(detailsItems.nth(0).locator('.details-item__text')).toHaveText('Patches');
await expect(detailsItems.nth(1).locator('.details-item__title')).toHaveText('Joined');
await expect(detailsItems.nth(1).locator('.details-item__text')).toHaveText('Oct 6, 2009');
await expect(detailsItems.nth(0).locator('.details-item-name__title')).toHaveText('Name');
// cspell:disable-next-line
await expect(detailsItems.nth(0).locator('.details-item-name__text')).toHaveText('Arthas Menethil');
await expect(detailsItems.nth(0).locator('.revoked-chip')).toBeVisible();
await expect(detailsItems.nth(1).locator('.details-item-name__title')).toHaveText('Joined');
await expect(detailsItems.nth(1).locator('.details-item-name__text')).toHaveText('Jul 3, 2002');
await expect(detailsItems.nth(2).locator('.details-item-name__title')).toHaveText('Revoked since');
await expect(detailsItems.nth(2).locator('.details-item-name__text')).toHaveText('Apr 7, 2022');
await expect(modal.locator('.workspace-list')).toBeHidden();
await expect(modal.locator('.workspace-empty')).toBeVisible();
await expect(modal.locator('.workspace-empty')).toHaveText('You have no workspaces in common with this user.');
Expand Down
1 change: 1 addition & 0 deletions newsfragments/8862.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Displays the revocation date in the user details modal