Skip to content

Commit 587bc32

Browse files
committed
Merge remote-tracking branch 'origin/develop' into develop
2 parents 726f730 + c61ac4e commit 587bc32

44 files changed

Lines changed: 1698 additions & 641 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"mixpanel-browser": "^2.47.0",
5858
"popper-max-size-modifier": "0.2.0",
5959
"qrcode.react": "^4.2.0",
60-
"qs": ">=6.14.1",
60+
"qs": ">=6.15.2",
6161
"react": "^18.3.1",
6262
"react-beautiful-dnd": "^13.1.1",
6363
"react-big-calendar": "^1.18.0",
@@ -190,7 +190,9 @@
190190
"js-yaml": ">=4.1.1",
191191
"lodash": ">=4.18.0",
192192
"picomatch": ">=4.0.4",
193-
"qs": ">=6.14.1",
193+
"postcss": ">=8.5.10",
194+
"qs": ">=6.15.2",
195+
"uuid": ">=14.0.0",
194196
"vite": ">=7.3.2",
195197
"yaml": ">=1.10.3"
196198
},

src/modules/Dashboard/api/api.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { AppletId, ActivityId, ActivityFlowId, Response, ResponseWithObject } fr
44
import { ExportDataResult } from 'shared/types';
55
import { DEFAULT_ROWS_PER_PAGE as SHARED_DEFAULT_ROWS_PER_PAGE, MAX_LIMIT } from 'shared/consts'; // TODO: replace MAX_LIMIT with infinity scroll
66
import { authApiClient } from 'shared/api/apiConfig';
7+
import { AuditEvent } from 'shared/types/auditEvent';
78

89
import {
910
TransferOwnershipType,
@@ -86,6 +87,7 @@ import {
8687
DeviceScheduleHistoryData,
8788
FlowItemHistoryParams,
8889
FlowItemHistoryData,
90+
ExportAuditLogs,
8991
} from './api.types';
9092
import { DEFAULT_API_RESULTS_PER_PAGE } from './api.const';
9193
import { SubjectDetailsWithDataAccess } from '../types';
@@ -800,6 +802,20 @@ export const getAppletVersionChangesApi = (
800802
signal?: AbortSignal,
801803
) => authApiClient.get(`/applets/${appletId}/versions/${version}/changes`, { signal });
802804

805+
/**
806+
* Get audit logs from API given a date range and applet ID
807+
* */
808+
export const getExportAuditLogsApi = ({
809+
appletId,
810+
fromDatetime,
811+
toDatetime,
812+
page = 1,
813+
limit = DEFAULT_API_RESULTS_PER_PAGE,
814+
}: ExportAuditLogs) =>
815+
authApiClient.get<Response<AuditEvent>>(`/audit/applets/${appletId}/events`, {
816+
params: { fromDatetime, toDatetime, page, limit },
817+
});
818+
803819
export const getExportDataApi = (
804820
{ appletId, page = 1, limit = DEFAULT_API_RESULTS_PER_PAGE, ...rest }: ExportData,
805821
signal?: AbortSignal,

src/modules/Dashboard/api/api.types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,14 @@ export type ExportData = AppletId & {
600600
includeEhr?: boolean;
601601
};
602602

603+
/** This is the type params for the export audit logs API request */
604+
export type ExportAuditLogs = AppletId & {
605+
fromDatetime?: string;
606+
toDatetime?: string;
607+
page?: number;
608+
limit?: number;
609+
};
610+
603611
export type ScheduleHistoryParams = AppletId & {
604612
respondentIds?: string[];
605613
subjectIds?: string[];

src/modules/Dashboard/components/HeaderOptions/HeaderOptions.test.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,22 @@ describe('HeaderOptions', () => {
5252
renderWithProviders(<HeaderOptions />, { preloadedState: getPreloadedState() });
5353
});
5454

55-
test('should open Export dialog when option is pressed', () => {
55+
test('should open Export options menu when export button is pressed', () => {
5656
fireEvent.click(screen.getByTestId('header-option-export-button'));
5757

58-
expect(screen.queryByTestId('export-data-settings')).toBeInTheDocument();
58+
expect(screen.queryByTestId('header-option-export-menu')).toBeInTheDocument();
59+
});
60+
61+
test('should see Response Data option in export options menu', () => {
62+
fireEvent.click(screen.getByTestId('header-option-export-button'));
63+
64+
expect(screen.queryByTestId('header-option-response-data-button')).toBeInTheDocument();
65+
});
66+
67+
test('should see Audit Logs option in export options menu', () => {
68+
fireEvent.click(screen.getByTestId('header-option-export-button'));
69+
70+
expect(screen.queryByTestId('header-option-audit-logs-button')).toBeInTheDocument();
5971
});
6072

6173
test('should contain link to settings page', () => {

src/modules/Dashboard/components/HeaderOptions/HeaderOptions.tsx

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,102 @@
1-
import { useState } from 'react';
1+
import { useRef, useState } from 'react';
22
import { Button, IconButton } from '@mui/material';
33
import { Link, generatePath, useParams } from 'react-router-dom';
44
import { useTranslation } from 'react-i18next';
55

66
import { page } from 'resources';
7-
import { Svg } from 'shared/components';
7+
import { Menu, Svg } from 'shared/components';
88
import { ExportDataSetting } from 'shared/features/AppletSettings';
99
import { StyledFlexTopCenter, variables } from 'shared/styles';
10-
import { Mixpanel, checkIfCanAccessData, checkIfCanEdit, MixpanelEventType } from 'shared/utils';
10+
import {
11+
Mixpanel,
12+
checkIfCanAccessData,
13+
checkIfCanEdit,
14+
MixpanelEventType,
15+
checkIfFullAccess,
16+
} from 'shared/utils';
1117
import { workspaces } from 'shared/state';
18+
import { AuditLogsExportSetting } from 'shared/features/AppletSettings/AuditLogsExportSetting/AuditLogsExportSetting';
1219

1320
export const HeaderOptions = () => {
14-
const [isExportOpen, setIsExportOpen] = useState(false);
21+
const [isResponseDataExportOpen, setIsResponseDataExportOpen] = useState(false);
22+
const [isAuditLogsExportOpen, setIsAuditLogsExportOpen] = useState(false);
23+
1524
const { t } = useTranslation('app');
1625
const { appletId } = useParams();
1726
const isSettingsSelected = location.pathname.includes('settings');
1827
const workspaceRoles = workspaces.useRolesData();
1928
const roles = appletId ? workspaceRoles?.data?.[appletId] : undefined;
29+
const [showExportMenu, setShowExportMenu] = useState<boolean>(false);
30+
const exportButtonRef = useRef<HTMLButtonElement>(null);
31+
32+
const handleOpenExportMenu = () => {
33+
const fullAccess = checkIfFullAccess(roles);
34+
35+
if (fullAccess) {
36+
setShowExportMenu(true);
37+
} else {
38+
setIsResponseDataExportOpen(true);
39+
Mixpanel.track({ action: MixpanelEventType.ExportDataClick });
40+
}
41+
};
42+
43+
const handleOpenAuditLogs = () => {
44+
setIsAuditLogsExportOpen(true);
45+
Mixpanel.track({ action: MixpanelEventType.ExportAuditLogsClick });
46+
};
2047

21-
const handleOpenExport = () => {
22-
setIsExportOpen(true);
48+
const handleOpenResponseData = () => {
49+
setIsResponseDataExportOpen(true);
2350
Mixpanel.track({ action: MixpanelEventType.ExportDataClick });
2451
};
2552

26-
const handleCloseExport = () => {
27-
setIsExportOpen(false);
53+
const handleCloseResponseData = () => {
54+
setIsResponseDataExportOpen(false);
55+
};
56+
57+
const handleCloseAuditLogsExport = () => {
58+
setIsAuditLogsExportOpen(false);
2859
};
2960

3061
const canAccessData = checkIfCanAccessData(roles);
3162
const canEdit = checkIfCanEdit(roles);
3263

64+
const getExportActions = () => [
65+
{
66+
icon: <Svg id="response-data" />,
67+
action: handleOpenResponseData,
68+
title: t('dataExport.responseData.menuCaption'),
69+
'data-testid': 'header-option-response-data-button',
70+
},
71+
{
72+
icon: <Svg id="audit-logs" />,
73+
action: handleOpenAuditLogs,
74+
title: t('dataExport.auditLogs.menuCaption'),
75+
'data-testid': 'header-option-audit-logs-button',
76+
},
77+
];
78+
3379
return canEdit || canAccessData ? (
3480
<StyledFlexTopCenter sx={{ gap: 1, ml: 'auto' }}>
3581
{canAccessData && (
3682
<Button
83+
ref={exportButtonRef}
3784
data-testid="header-option-export-button"
38-
onClick={handleOpenExport}
85+
onClick={handleOpenExportMenu}
3986
startIcon={<Svg id="export" width={18} height={18} />}
4087
sx={{ color: variables.palette.on_surface_variant }}
4188
>
4289
{t('export')}
4390
</Button>
4491
)}
45-
92+
{showExportMenu && (
93+
<Menu
94+
data-testid="header-option-export-menu"
95+
anchorEl={exportButtonRef.current}
96+
onClose={() => setShowExportMenu(false)}
97+
menuItems={getExportActions()}
98+
/>
99+
)}
46100
{canEdit && (
47101
<IconButton
48102
component={Link}
@@ -55,9 +109,15 @@ export const HeaderOptions = () => {
55109
)}
56110

57111
<ExportDataSetting
58-
isExportSettingsOpen={isExportOpen}
59-
onExportSettingsClose={handleCloseExport}
60-
data-testid={'export-data'}
112+
isExportSettingsOpen={isResponseDataExportOpen}
113+
onExportSettingsClose={handleCloseResponseData}
114+
data-testid={'response-data-export'}
115+
/>
116+
<AuditLogsExportSetting
117+
isExportSettingsOpen={isAuditLogsExportOpen}
118+
onExportSettingsClose={handleCloseAuditLogsExport}
119+
data-testid={'audit-logs-export'}
120+
onExportPopupClose={() => setIsAuditLogsExportOpen(false)}
61121
/>
62122
</StyledFlexTopCenter>
63123
) : null;

src/modules/Dashboard/features/Applet/Popups/AddParticipantPopup/AddParticipantForm/AddParticipantForm.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,27 @@ describe('AddParticipantForm component tests', () => {
6464

6565
expect(mockOnSubmit).toHaveBeenCalled();
6666
});
67+
68+
test('Invitation language dropdown lists every ApiLanguages value with its translated label', () => {
69+
render(<AddParticipantFormTest accountType={AccountType.Full} />);
70+
71+
const trigger = screen.getByTestId(`${dataTestid}-lang`).querySelector('[role="combobox"]');
72+
expect(trigger).not.toBeNull();
73+
fireEvent.mouseDown(trigger as Element);
74+
75+
const expectedLabels: Record<ApiLanguages, string> = {
76+
[ApiLanguages.EN]: 'English',
77+
[ApiLanguages.FR]: 'French',
78+
[ApiLanguages.EL]: 'Greek',
79+
[ApiLanguages.ES]: 'Spanish',
80+
[ApiLanguages.PT]: 'Portuguese',
81+
[ApiLanguages.AF]: 'Afrikaans',
82+
[ApiLanguages.XH]: 'Xhosa',
83+
[ApiLanguages.ZU]: 'Zulu',
84+
};
85+
86+
Object.values(ApiLanguages).forEach((lang) => {
87+
expect(screen.getByRole('option', { name: expectedLabels[lang] })).toBeInTheDocument();
88+
});
89+
});
6790
});

src/modules/Dashboard/features/Managers/Popups/AddManagerPopup/AddManagerForm/AddManagerForm.test.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
44

55
import { mockedFullParticipant1, mockedFullParticipant2 } from 'shared/mock';
66
import { ParticipantTag, Roles } from 'shared/consts';
7+
import { ApiLanguages } from 'api';
78

89
import { AddManagerForm } from './AddManagerForm';
910
import { AddManagerFormValues } from '../AddManagerPopup.types';
@@ -109,4 +110,27 @@ describe('AddManagerForm component tests', () => {
109110

110111
expect(mockOnSubmit).toHaveBeenCalled();
111112
});
113+
114+
test('Invitation language dropdown lists every ApiLanguages value with its translated label', () => {
115+
render(<AddManagerFormTest />);
116+
117+
const trigger = screen.getByTestId(`${dataTestid}-lang`).querySelector('[role="combobox"]');
118+
expect(trigger).not.toBeNull();
119+
fireEvent.mouseDown(trigger as Element);
120+
121+
const expectedLabels: Record<ApiLanguages, string> = {
122+
[ApiLanguages.EN]: 'English',
123+
[ApiLanguages.FR]: 'French',
124+
[ApiLanguages.EL]: 'Greek',
125+
[ApiLanguages.ES]: 'Spanish',
126+
[ApiLanguages.PT]: 'Portuguese',
127+
[ApiLanguages.AF]: 'Afrikaans',
128+
[ApiLanguages.XH]: 'Xhosa',
129+
[ApiLanguages.ZU]: 'Zulu',
130+
};
131+
132+
Object.values(ApiLanguages).forEach((lang) => {
133+
expect(screen.getByRole('option', { name: expectedLabels[lang] })).toBeInTheDocument();
134+
});
135+
});
112136
});

src/resources/app-en.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@
203203
"addUsers": "Add Users",
204204
"addViaCSV": "Add via CSV",
205205
"advancedSettings": "Advanced Settings",
206+
"af": "Afrikaans",
206207
"ageQuestion": "How old are you?<br><br>*Please provide your response as accurately as possible. The information you provide is important for ensuring the accuracy of your results. If you have any concerns about how your information will be used, please refer to our Terms of Service.*",
207208
"ageFieldTypeLabel": "Age field type:",
208209
"ageFieldTypeText": "Text Field",
@@ -464,6 +465,22 @@
464465
"dashboard": "Dashboard",
465466
"dataExport": {
466467
"title": "Data Export",
468+
"responseData": {
469+
"menuCaption": "Response Data"
470+
},
471+
"auditLogs": {
472+
"menuCaption": "Security Audit Logs",
473+
"title": "{{name}} security audit logs",
474+
"header": "Export Security Audit Logs",
475+
"label": "Export: ",
476+
"description": "Download logs containing applet activity for analysis in SIEM software.",
477+
"button": "Export",
478+
"supplementaryFiles": {
479+
"includes": {
480+
"tsv": "Include human readable TSV file"
481+
}
482+
}
483+
},
467484
"data": "Data",
468485
"dataExported": {
469486
"responsesOnly": "Responses Only",
@@ -1797,12 +1814,14 @@
17971814
"workspaceSelection": "Select Workspace",
17981815
"workspacesLoading": "Checking your workspaces",
17991816
"workspaceTooltip": "Workspace name displays to the managers you have invited to your applet within their interface.",
1817+
"xh": "Xhosa",
18001818
"year": "Year",
18011819
"years": "Years",
18021820
"yes": "Yes",
18031821
"yesAuthorize": "Yes, authorize",
18041822
"yesRemove": "Yes, Remove",
18051823
"youNeedToAuthorize": "You Need to Authorize",
1824+
"zu": "Zulu",
18061825
"positiveIntegerRequired": "A positive integer is required",
18071826
"positiveIntegerOrZeroRequired": "A positive integer or 0 is required",
18081827
"fromToHint": "From {{min}} to {{max}}",

src/resources/app-fr.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@
203203
"addUsers": "Ajouter des utilisateurs",
204204
"addViaCSV": "Ajouter via CSV",
205205
"advancedSettings": "Paramètres avancés",
206+
"af": "Afrikaans",
206207
"ageQuestion": "Quel âge avez-vous?<br><br>*Veuillez fournir votre réponse aussi précisément que possible. Les informations que vous fournissez sont importantes pour garantir l'exactitude de vos résultats. Si vous avez des inquiétudes quant à la manière dont vos informations seront utilisées, veuillez vous référer à nos Conditions d'utilisation.*",
207208
"ageFieldTypeLabel": "Type de champ d'âge :",
208209
"ageFieldTypeText": "Champ de texte",
@@ -464,6 +465,22 @@
464465
"dashboard": "Tableau de bord",
465466
"dataExport": {
466467
"title": "Export de données",
468+
"responseData": {
469+
"menuCaption": "Données de réponse"
470+
},
471+
"auditLogs": {
472+
"menuCaption": "Journaux d'audit de sécurité",
473+
"title": "Journaux d'audit de sécurité de {{name}}",
474+
"header": "Exporter les journaux d'audit de sécurité",
475+
"label": "Exporter : ",
476+
"description": "Téléchargez les journaux contenant l'activité de l'applet pour analyse dans un logiciel SIEM.",
477+
"button": "Exporter",
478+
"supplementaryFiles": {
479+
"includes": {
480+
"tsv": "Inclure un fichier TSV lisible"
481+
}
482+
}
483+
},
467484
"data": "Données",
468485
"dataExported": {
469486
"responsesOnly": "Réponses uniquement",
@@ -1796,12 +1813,14 @@
17961813
"workspaceSelection": "Sélectionnez l'espace de travail",
17971814
"workspacesLoading": "Vérification de vos espaces de travail",
17981815
"workspaceTooltip": "Le nom de l'espace de travail s'affiche pour les gestionnaires que vous avez invités à votre applet dans leur interface.",
1816+
"xh": "Xhosa",
17991817
"year": "Année",
18001818
"years": "Années",
18011819
"yes": "Oui",
18021820
"yesAuthorize": "Oui, autoriser",
18031821
"yesRemove": "Oui, supprimer",
18041822
"youNeedToAuthorize": "Vous devez autoriser",
1823+
"zu": "Zoulou",
18051824
"positiveIntegerRequired": "Un entier positif est requis",
18061825
"positiveIntegerOrZeroRequired": "Un entier positif ou 0 est requis",
18071826
"fromToHint": "De {{min}} à {{max}}",

src/shared/api/api.const.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ export enum ApiLanguages {
44
EL = 'el',
55
ES = 'es',
66
PT = 'pt',
7+
AF = 'af',
8+
XH = 'xh',
9+
ZU = 'zu',
710
}
811

912
export const DEFAULT_CONFIG = {

0 commit comments

Comments
 (0)