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

[TASK-1059] Handle submission groups in Access Log UI #5116

Open
wants to merge 23 commits into
base: TASK-869-group-at-fetch
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0af76c1
refactor: make access logs a proxy class
rgraber Sep 3, 2024
ee2e727
fixup!: new tests
rgraber Sep 3, 2024
6587c70
fixup!: cleanup
rgraber Sep 3, 2024
f24b102
fixup!: authorized app
rgraber Sep 3, 2024
e19279a
fixup!: fix rebase artifact
rgraber Sep 3, 2024
a98ae4b
Add Access Log section to security route, add access log query and us…
magicznyleszek Sep 13, 2024
a3bb916
update styles of all security route sections
magicznyleszek Sep 16, 2024
0c797d0
simplify MFA section styles and HTML by removing BEM and making it us…
magicznyleszek Sep 16, 2024
b2b222b
fixup!: use lowercase user
rgraber Sep 16, 2024
4ba78ca
fixup!: migration
rgraber Sep 17, 2024
fa6bfdf
fixup!: format
rgraber Sep 17, 2024
e039199
feat: group submission logs when fetching access logs
rgraber Sep 17, 2024
d6b6ae6
fixup!: rm old stuff
rgraber Sep 17, 2024
36b4388
fixup!: endpoint documentation
rgraber Sep 17, 2024
8040ec7
Merge branch 'task858-universal-table' into task857-access-log-ui
magicznyleszek Sep 18, 2024
195d65d
hook up log out all button to API
magicznyleszek Sep 18, 2024
6f4a476
fixup!: i will refactor you and everything you love
rgraber Sep 18, 2024
8b9576d
tiny fixes for failing access log endpoint
magicznyleszek Sep 18, 2024
6f2bacc
Merge branch 'task857-access-log-ui' into task1059-access-log-ui-subm…
magicznyleszek Sep 18, 2024
2073b24
rename access log to access logs for consistency with BE code
magicznyleszek Sep 18, 2024
85d9dd1
Merge branch 'task857-access-log-ui' into task1059-access-log-ui-subm…
magicznyleszek Sep 18, 2024
ef7b976
add flexibility to cellFormatter fn for UniversalTable and display pr…
magicznyleszek Sep 18, 2024
31c8c57
fix Project Views switcher text being cut
magicznyleszek Sep 19, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Libraries
import React from 'react';

// Partial components
import Button from 'js/components/common/button';
import PaginatedQueryUniversalTable from 'js/universalTable/paginatedQueryUniversalTable.component';

// Utilities
import useAccessLogsQuery, {type AccessLog} from 'js/query/queries/accessLogs.query';
import {formatTime} from 'js/utils';
import sessionStore from 'js/stores/session';

// Styles
import securityStyles from 'js/account/security/securityRoute.module.scss';

export default function AccessLogsSection() {
function logOutAllSessions() {
sessionStore.logOutAll();
}

return (
<>
<header className={securityStyles.securityHeader}>
<h2 className={securityStyles.securityHeaderText}>
{t('Recent account activity')}
</h2>

<div className={securityStyles.securityHeaderActions}>
<Button
type='text'
size='m'
onClick={logOutAllSessions}
label={t('Log out of all devices')}
startIcon='logout'
/>
</div>
</header>

<PaginatedQueryUniversalTable<AccessLog>
queryHook={useAccessLogsQuery}
columns={[
// The `key`s of these columns are matching the `AccessLog` interface
// properties (from `accessLogs.query.ts` file) using dot notation.
{
key: 'metadata.source',
label: t('Source'),
cellFormatter: (log: AccessLog) => {
if (log.metadata.auth_type === 'submission-group') {
return t('Data Submissions (##count##)').replace('##count##', String(log.count));
} else {
return log.metadata.source;
}
},
},
{
key: 'date_created',
label: t('Last activity'),
cellFormatter: (log: AccessLog) => {
return formatTime(log.date_created);
},
},
{key: 'metadata.ip_address', label: t('IP Address')},
]}
/>
</>
);
}
53 changes: 31 additions & 22 deletions jsapp/js/account/security/apiToken/apiTokenSection.component.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
// Libraries
import React, {
useState,
useEffect,
} from 'react';
import {dataInterface} from 'js/dataInterface';
import cx from 'classnames';

// Partial components
import TextBox from 'js/components/common/textBox';
import Button from 'js/components/common/button';

// Utils
import {dataInterface} from 'js/dataInterface';
import {notify} from 'js/utils';

// Styles
import styles from './apiTokenSection.module.scss';
import securityStyles from 'js/account/security/securityRoute.module.scss';

const HIDDEN_TOKEN_VALUE = '*'.repeat(40);

Expand Down Expand Up @@ -42,28 +51,28 @@ export default function ApiTokenDisplay() {
}, [isVisible]);

return (
<div className={styles.root}>
<div className={styles.titleSection}>
<h2 className={styles.title}>{t('API Key')}</h2>
</div>

<div className={styles.bodySection}>
<TextBox
type={isVisible && !isFetching && token !== null ? 'text' : 'password'}
value={token !== null ? token : HIDDEN_TOKEN_VALUE}
readOnly
/>
</div>
<section className={securityStyles.securitySection}>
<div className={securityStyles.securitySectionTitle}>
<h2 className={securityStyles.securitySectionTitleText}>{t('API Key')}</h2>
</div>

<div className={styles.optionsSection}>
<Button
label='Display'
size='m'
type='secondary'
onClick={toggleTokenVisibility}
/>
</div>
</div>
<div className={cx(securityStyles.securitySectionBody, styles.body)}>
<TextBox
type={isVisible && !isFetching && token !== null ? 'text' : 'password'}
value={token !== null ? token : HIDDEN_TOKEN_VALUE}
readOnly
className={styles.token}
/>
</div>

<div className={styles.options}>
<Button
label='Display'
size='m'
type='primary'
onClick={toggleTokenVisibility}
/>
</div>
</section>
);
}
60 changes: 10 additions & 50 deletions jsapp/js/account/security/apiToken/apiTokenSection.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,19 @@
@use 'scss/sizes';
@use 'scss/libs/_mdl';

.root {
margin-top: sizes.$x20;
margin-bottom: sizes.$x60;
padding-top: sizes.$x14;
display: flex;
align-items: baseline;
column-gap: sizes.$x16;
border-top: sizes.$x1 solid;
border-color: colors.$kobo-gray-300;
}

.titleSection {
flex: 2;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
}

.title {
margin: 0;
color: colors.$kobo-gray-700;
font-weight: 600;
line-height: 1.6;
.body {
flex: 5;
}

.bodySection {
flex: 6;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
padding-left: sizes.$x4;

label {
display: inline-block;
vertical-align: top;
width: 100%;

input {
// API token display
font-family: mdl.$font_mono;
// 40 characters + padding + border
max-width: calc(40ch + 22px);
}
.token {
input {
// API token display
font-family: mdl.$font_mono;
}
}

.optionsSection {
flex: 2;
text-align: right;

button {
display: inline-block;
}
.options {
flex: 3;
display: flex;
justify-content: flex-end;
}
58 changes: 35 additions & 23 deletions jsapp/js/account/security/email/emailSection.component.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
// Libraries
import React, {useEffect, useState} from 'react';
import cx from 'classnames';

// Stores and email related
import sessionStore from 'js/stores/session';
import {
getUserEmails,
setUserEmail,
deleteUnverifiedUserEmails,
} from './emailSection.api';
import type {EmailResponse} from './emailSection.api';
import style from './emailSection.module.scss';

// Partial components
import Button from 'jsapp/js/components/common/button';
import TextBox from 'jsapp/js/components/common/textBox';
import Icon from 'jsapp/js/components/common/icon';
import {formatTime} from 'jsapp/js/utils';
import {notify} from 'js/utils';

// Utils
import {formatTime, notify} from 'js/utils';

// Styles
import styles from './emailSection.module.scss';
import securityStyles from 'js/account/security/securityRoute.module.scss';

interface EmailState {
emails: EmailResponse[];
Expand All @@ -23,9 +33,14 @@ interface EmailState {
export default function EmailSection() {
const [session] = useState(() => sessionStore);

let initialEmail = '';
if ('email' in session.currentAccount) {
initialEmail = session.currentAccount.email;
}

const [email, setEmail] = useState<EmailState>({
emails: [],
newEmail: '',
newEmail: initialEmail,
refreshedEmail: false,
refreshedEmailDate: '',
});
Expand Down Expand Up @@ -103,26 +118,31 @@ export default function EmailSection() {
);

return (
<div className={style.root}>
<div className={style.titleSection}>
<h2 className={style.title}>{t('Email address')}</h2>
<section className={securityStyles.securitySection}>
<div className={securityStyles.securitySectionTitle}>
<h2 className={securityStyles.securitySectionTitleText}>{t('Email address')}</h2>
</div>

<div className={style.bodySection}>
<div className={cx(securityStyles.securitySectionBody, styles.body)}>
{!session.isPending &&
session.isInitialLoadComplete &&
'email' in currentAccount && (
<p className={style.currentEmail}>{currentAccount.email}</p>
<TextBox
value={email.newEmail}
placeholder={t('Type new email address')}
onChange={onTextFieldChange.bind(onTextFieldChange)}
type='email'
/>
)}

{unverifiedEmail?.email &&
!session.isPending &&
session.isInitialLoadComplete &&
'email' in currentAccount && (
<>
<div className={style.unverifiedEmail}>
<div className={styles.unverifiedEmail}>
<Icon name='alert' />
<p className={style['blurb']}>
<p className={styles.blurb}>
<strong>
{t('Check your email ##UNVERIFIED_EMAIL##. ').replace(
'##UNVERIFIED_EMAIL##',
Expand All @@ -136,7 +156,7 @@ export default function EmailSection() {
</p>
</div>

<div className={style.editEmail}>
<div className={styles.editEmail}>
<Button
label='Resend'
size='m'
Expand Down Expand Up @@ -167,27 +187,19 @@ export default function EmailSection() {
</div>

<form
className={style.optionsSection}
className={styles.options}
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
{/*TODO: Move TextBox into a modal--it messes up the flow of the row right now*/}
<TextBox
value={email.newEmail}
placeholder={t('Type new email address')}
onChange={onTextFieldChange.bind(onTextFieldChange)}
type='email'
/>

<Button
label='Change'
size='m'
type='secondary'
type='primary'
onClick={handleSubmit}
/>
</form>
</div>
</section>
);
}
43 changes: 4 additions & 39 deletions jsapp/js/account/security/email/emailSection.module.scss
Original file line number Diff line number Diff line change
@@ -1,49 +1,14 @@
@use 'scss/colors';
@use 'scss/sizes';
@use 'scss/libs/_mdl';

.root {
margin-top: sizes.$x20;
margin-bottom: sizes.$x60;
padding-top: sizes.$x14;
display: flex;
align-items: baseline;
column-gap: sizes.$x16;
border-top: sizes.$x1 solid;
border-color: colors.$kobo-gray-300;
}

.titleSection {
flex: 2;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
}

.title {
margin: 0;
color: colors.$kobo-gray-700;
font-weight: 600;
line-height: 1.6;
}

.bodySection {
.body {
flex: 5;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
padding-left: sizes.$x16;
}

.optionsSection {
flex: 3; // widen for email input

// stack input and button, right-aligned.
.options {
flex: 3;
display: flex;
flex-direction: column;
row-gap: sizes.$x12;
align-items: flex-end;
justify-content: flex-end;
}

.currentEmail {
Expand Down
Loading