From a124c11de86cde449460c95de984a5de27e98e56 Mon Sep 17 00:00:00 2001
From: alexd-bes <129009580+alexd-bes@users.noreply.github.com>
Date: Fri, 24 May 2024 09:17:48 +1200
Subject: [PATCH 01/10] Make links
---
.../admin-panel/src/routes/surveys/surveys.js | 10 ++++++
.../table/columnTypes/ExternalLinkButton.jsx | 32 +++++++++++++++++++
.../generateConfigForColumnType.jsx | 3 ++
.../utilities/makeSubstitutionsInString.js | 2 +-
4 files changed, 46 insertions(+), 1 deletion(-)
create mode 100644 packages/admin-panel/src/table/columnTypes/ExternalLinkButton.jsx
diff --git a/packages/admin-panel/src/routes/surveys/surveys.js b/packages/admin-panel/src/routes/surveys/surveys.js
index a926b187c0..6f7e07e297 100644
--- a/packages/admin-panel/src/routes/surveys/surveys.js
+++ b/packages/admin-panel/src/routes/surveys/surveys.js
@@ -5,6 +5,8 @@
import { SurveyEditFields } from '../../surveys/SurveyEditFields';
+const { REACT_APP_DATATRAK_WEB_URL } = import.meta.env;
+
const RESOURCE_NAME = { singular: 'survey' };
const PERIOD_GRANULARITIES = [
@@ -205,6 +207,14 @@ const SURVEY_COLUMNS = [
Header: 'Survey group',
source: 'survey_group.name',
},
+ {
+ Header: 'Preview',
+ type: 'externalLink',
+ actionConfig: {
+ url: `${REACT_APP_DATATRAK_WEB_URL}/survey?projectCode={project.code}`,
+ title: 'Preview survey',
+ },
+ },
{
Header: 'Export',
type: 'export',
diff --git a/packages/admin-panel/src/table/columnTypes/ExternalLinkButton.jsx b/packages/admin-panel/src/table/columnTypes/ExternalLinkButton.jsx
new file mode 100644
index 0000000000..054c60cdcc
--- /dev/null
+++ b/packages/admin-panel/src/table/columnTypes/ExternalLinkButton.jsx
@@ -0,0 +1,32 @@
+/*
+ * Tupaia
+ * Copyright (c) 2017 - 2024 Beyond Essential Systems Pty Ltd
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link } from '@material-ui/core';
+import { OpenInNewRounded } from '@material-ui/icons';
+import { ColumnActionButton } from './ColumnActionButton';
+import { makeSubstitutionsInString } from '../../utilities';
+
+export const ExternalLinkButton = ({ actionConfig, row }) => {
+ const fullUrl = makeSubstitutionsInString(actionConfig.url, row.original);
+
+ return (
+
+
+
+ );
+};
+
+ExternalLinkButton.propTypes = {
+ actionConfig: PropTypes.object.isRequired,
+ row: PropTypes.object.isRequired,
+};
diff --git a/packages/admin-panel/src/table/columnTypes/generateConfigForColumnType.jsx b/packages/admin-panel/src/table/columnTypes/generateConfigForColumnType.jsx
index 62450aac4e..d6651626df 100644
--- a/packages/admin-panel/src/table/columnTypes/generateConfigForColumnType.jsx
+++ b/packages/admin-panel/src/table/columnTypes/generateConfigForColumnType.jsx
@@ -15,6 +15,7 @@ import { BulkEditButton } from './BulkEditButton';
import { TestDatabaseConnectionButton } from './TestDatabaseConnectionButton';
import { QrCodeButton } from './QrCodeButton';
import { ResubmitSurveyResponseButton } from './ResubmitSurveyResponseButton';
+import { ExternalLinkButton } from './ExternalLinkButton';
const generateCustomCell = (CustomCell, actionConfig, reduxId) => props =>
;
@@ -38,6 +39,7 @@ const CUSTOM_CELL_COMPONENTS = {
testDatabaseConnection: TestDatabaseConnectionButton,
qrCode: QrCodeButton,
resubmitSurveyResponse: ResubmitSurveyResponseButton,
+ externalLink: ExternalLinkButton,
};
const BUTTON_COLUMN_TYPES = [
@@ -49,6 +51,7 @@ const BUTTON_COLUMN_TYPES = [
'qrCode',
'testDatabaseConnection',
'bulkEdit',
+ 'externalLink',
];
export const generateConfigForColumnType = (type, actionConfig, reduxId) => {
diff --git a/packages/admin-panel/src/utilities/makeSubstitutionsInString.js b/packages/admin-panel/src/utilities/makeSubstitutionsInString.js
index 7028b17436..c16fb552a7 100644
--- a/packages/admin-panel/src/utilities/makeSubstitutionsInString.js
+++ b/packages/admin-panel/src/utilities/makeSubstitutionsInString.js
@@ -4,7 +4,7 @@
*/
const extractParams = template =>
- [...template.matchAll(/\{(\w+)\}/gi)].map(matchArray => matchArray[1]);
+ [...template.matchAll(/(?<=\{)(.*?)(?=\})/gi)].map(matchArray => matchArray[1]);
export const makeSubstitutionsInString = (template, variables) => {
const params = extractParams(template);
From 92a00a192af4a9058a130e09e5c2d3988c943176 Mon Sep 17 00:00:00 2001
From: alexd-bes <129009580+alexd-bes@users.noreply.github.com>
Date: Fri, 24 May 2024 09:39:25 +1200
Subject: [PATCH 02/10] Use projectId
---
packages/admin-panel/src/routes/surveys/surveys.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/admin-panel/src/routes/surveys/surveys.js b/packages/admin-panel/src/routes/surveys/surveys.js
index 6f7e07e297..bdd8bf504e 100644
--- a/packages/admin-panel/src/routes/surveys/surveys.js
+++ b/packages/admin-panel/src/routes/surveys/surveys.js
@@ -199,6 +199,11 @@ const SURVEY_COLUMNS = [
SURVEY_FIELDS.project,
SURVEY_FIELDS.name,
SURVEY_FIELDS.code,
+ {
+ Header: 'Project ID',
+ source: 'project.id',
+ show: false,
+ },
{
Header: 'Permission group',
source: 'permission_group.name',
@@ -211,7 +216,7 @@ const SURVEY_COLUMNS = [
Header: 'Preview',
type: 'externalLink',
actionConfig: {
- url: `${REACT_APP_DATATRAK_WEB_URL}/survey?projectCode={project.code}`,
+ url: `${REACT_APP_DATATRAK_WEB_URL}/survey?projectId={project.id}`,
title: 'Preview survey',
},
},
From 12b379c797e260dc3797e439144570c3e0567d71 Mon Sep 17 00:00:00 2001
From: alexd-bes <129009580+alexd-bes@users.noreply.github.com>
Date: Fri, 24 May 2024 09:55:14 +1200
Subject: [PATCH 03/10] Update user preferences if project id is in url
---
.../SurveySelectPage/SurveySelectPage.tsx | 39 +++++++++++++++----
1 file changed, 31 insertions(+), 8 deletions(-)
diff --git a/packages/datatrak-web/src/views/SurveySelectPage/SurveySelectPage.tsx b/packages/datatrak-web/src/views/SurveySelectPage/SurveySelectPage.tsx
index 3faf88c5b6..6844cc8023 100644
--- a/packages/datatrak-web/src/views/SurveySelectPage/SurveySelectPage.tsx
+++ b/packages/datatrak-web/src/views/SurveySelectPage/SurveySelectPage.tsx
@@ -4,6 +4,7 @@
*/
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router';
+import { useSearchParams } from 'react-router-dom';
import styled from 'styled-components';
import { DialogActions, Paper, Typography } from '@material-ui/core';
import { SpinningLoader } from '@tupaia/ui-components';
@@ -102,6 +103,8 @@ const sortAlphanumerically = (a: ListItemType, b: ListItemType) => {
export const SurveySelectPage = () => {
const navigate = useNavigate();
const [selectedSurvey, setSelectedSurvey] = useState(null);
+ const [urlSearchParams] = useSearchParams();
+ const urlProjectId = urlSearchParams.get('projectId');
const {
countries,
selectedCountry,
@@ -112,7 +115,7 @@ export const SurveySelectPage = () => {
const navigateToSurvey = () => {
navigate(`/survey/${selectedCountry?.code}/${selectedSurvey?.value}`);
};
- const { mutate: updateUser, isLoading: isUpdatingUser } = useEditUser(navigateToSurvey);
+ const { mutateAsync: updateUser, isLoading: isUpdatingUser } = useEditUser();
const user = useCurrentUserContext();
const { data: surveys, isLoading } = useProjectSurveys(user.projectId, selectedCountry?.name);
@@ -162,7 +165,12 @@ export const SurveySelectPage = () => {
const handleSelectSurvey = () => {
if (countryHasUpdated) {
// update user with new country. If the user goes 'back' and doesn't select a survey, and does not yet have a country selected, that's okay because it will be set whenever they next select a survey
- updateUser({ countryId: selectedCountry?.id });
+ updateUser(
+ { countryId: selectedCountry?.id },
+ {
+ onSuccess: navigateToSurvey,
+ },
+ );
} else navigateToSurvey();
};
@@ -173,7 +181,20 @@ export const SurveySelectPage = () => {
}
}, [JSON.stringify(surveys)]);
- const showLoader = isLoading || isLoadingCountries || isUpdatingUser;
+ useEffect(() => {
+ const updateUserProject = async () => {
+ if (urlProjectId && user.projectId !== urlProjectId) {
+ updateUser({ projectId: urlProjectId });
+ }
+ };
+ updateUserProject();
+ }, [urlProjectId]);
+
+ const showLoader =
+ isLoading ||
+ isLoadingCountries ||
+ isUpdatingUser ||
+ (urlProjectId && urlProjectId !== user?.projectId);
return (
@@ -181,11 +202,13 @@ export const SurveySelectPage = () => {
Select survey
Select a survey from the list below
-
+ {!showLoader && (
+
+ )}
{showLoader ? (
From 898a18aad809151a3dddf179bc53e6ea63c53846 Mon Sep 17 00:00:00 2001
From: alexd-bes <129009580+alexd-bes@users.noreply.github.com>
Date: Fri, 24 May 2024 09:59:48 +1200
Subject: [PATCH 04/10] Add comment
---
.../src/views/SurveySelectPage/SurveySelectPage.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/datatrak-web/src/views/SurveySelectPage/SurveySelectPage.tsx b/packages/datatrak-web/src/views/SurveySelectPage/SurveySelectPage.tsx
index 6844cc8023..35255d6a26 100644
--- a/packages/datatrak-web/src/views/SurveySelectPage/SurveySelectPage.tsx
+++ b/packages/datatrak-web/src/views/SurveySelectPage/SurveySelectPage.tsx
@@ -194,7 +194,7 @@ export const SurveySelectPage = () => {
isLoading ||
isLoadingCountries ||
isUpdatingUser ||
- (urlProjectId && urlProjectId !== user?.projectId);
+ (urlProjectId && urlProjectId !== user?.projectId); // in this case the user will be updating and all surveys etc will be reloaded, so showing a loader when this is the case means a more seamless experience
return (
From 9915f8ec10af1b12169cf822475055630c00124b Mon Sep 17 00:00:00 2001
From: alexd-bes <129009580+alexd-bes@users.noreply.github.com>
Date: Thu, 11 Jul 2024 11:58:11 +1200
Subject: [PATCH 05/10] Allow country codes to be fetched for surveys
---
.../src/apiV2/surveys/GETSurveys.js | 27 ++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/packages/central-server/src/apiV2/surveys/GETSurveys.js b/packages/central-server/src/apiV2/surveys/GETSurveys.js
index f4626ac224..e2b44588d3 100644
--- a/packages/central-server/src/apiV2/surveys/GETSurveys.js
+++ b/packages/central-server/src/apiV2/surveys/GETSurveys.js
@@ -25,6 +25,7 @@ import { processColumns } from '../GETHandler/helpers';
const SURVEY_QUESTIONS_COLUMN = 'surveyQuestions';
const COUNTRY_NAMES_COLUMN = 'countryNames';
+const COUNTRY_CODES_COLUMN = 'countryCodes';
export class GETSurveys extends GETHandler {
permissionsFilteredInternally = true;
@@ -44,11 +45,13 @@ export class GETSurveys extends GETHandler {
// 2. Add countryNames
const countryNames = await this.getSurveyCountryNames([surveyId]);
+ const countryCodes = await this.getSurveyCountryCodes([surveyId]);
return {
...survey,
surveyQuestions: surveyQuestionsValues[surveyId],
countryNames: countryNames[surveyId],
+ countryCodes: countryCodes[surveyId],
};
}
@@ -65,10 +68,16 @@ export class GETSurveys extends GETHandler {
records.filter(record => record.id).map(record => record.id),
);
+ // 3. Add countryCodes
+ const countryCodes = await this.getSurveyCountryCodes(
+ records.filter(record => record.id).map(record => record.id),
+ );
+
return records.map(record => ({
...record,
surveyQuestions: surveyQuestionsValues[record.id],
countryNames: countryNames[record.id],
+ countryCodes: countryCodes[record.id],
}));
}
@@ -104,9 +113,10 @@ export class GETSurveys extends GETHandler {
// If we've requested specific columns, we allow skipping these fields by not requesting them
this.includeQuestions = parsedColumns.includes(SURVEY_QUESTIONS_COLUMN);
this.includeCountryNames = parsedColumns.includes(COUNTRY_NAMES_COLUMN);
+ this.includeCountryCodes = parsedColumns.includes(COUNTRY_CODES_COLUMN);
const unprocessedColumns = parsedColumns.filter(
- col => ![SURVEY_QUESTIONS_COLUMN, COUNTRY_NAMES_COLUMN].includes(col),
+ col => ![SURVEY_QUESTIONS_COLUMN, COUNTRY_NAMES_COLUMN, COUNTRY_CODES_COLUMN].includes(col),
);
return processColumns(this.models, unprocessedColumns, this.recordType);
}
@@ -162,6 +172,21 @@ export class GETSurveys extends GETHandler {
);
return Object.fromEntries(rows.map(row => [row.id, row.country_names]));
}
+
+ async getSurveyCountryCodes(surveyIds) {
+ if (surveyIds.length === 0 || !this.includeCountryCodes) return {};
+ const rows = await this.database.executeSql(
+ `
+ SELECT survey.id, array_agg(country.code) as country_codes
+ FROM survey
+ LEFT JOIN country ON (country.id = any(survey.country_ids))
+ WHERE survey.id in (${surveyIds.map(() => '?').join(',')})
+ GROUP BY survey.id;
+ `,
+ surveyIds,
+ );
+ return Object.fromEntries(rows.map(row => [row.id, row.country_codes]));
+ }
}
const getAggregatedQuestions = rawResults => {
From 85cbd23c76108223b349586d2b6248f0008da344 Mon Sep 17 00:00:00 2001
From: alexd-bes <129009580+alexd-bes@users.noreply.github.com>
Date: Thu, 11 Jul 2024 11:58:39 +1200
Subject: [PATCH 06/10] Link directly to survey
---
packages/admin-panel/src/routes/surveys/surveys.js | 11 ++++++++++-
.../src/table/columnTypes/ExternalLinkButton.jsx | 9 ++++++++-
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/packages/admin-panel/src/routes/surveys/surveys.js b/packages/admin-panel/src/routes/surveys/surveys.js
index 0fe48c9eca..b5759a762c 100644
--- a/packages/admin-panel/src/routes/surveys/surveys.js
+++ b/packages/admin-panel/src/routes/surveys/surveys.js
@@ -177,6 +177,11 @@ const SURVEY_COLUMNS = [
source: 'project.id',
show: false,
},
+ {
+ Header: 'countries',
+ source: 'countryCodes',
+ show: false,
+ },
{
Header: 'Permission group',
source: 'permission_group.name',
@@ -189,7 +194,11 @@ const SURVEY_COLUMNS = [
Header: 'Preview',
type: 'externalLink',
actionConfig: {
- url: `${REACT_APP_DATATRAK_WEB_URL}/survey?projectId={project.id}`,
+ generateUrl: row => {
+ const { code, countryCodes } = row;
+ if (!countryCodes || countryCodes.length === 0) return null;
+ return `${REACT_APP_DATATRAK_WEB_URL}/survey/${countryCodes[0]}/${code}/1`;
+ },
title: 'Preview survey',
},
},
diff --git a/packages/admin-panel/src/table/columnTypes/ExternalLinkButton.jsx b/packages/admin-panel/src/table/columnTypes/ExternalLinkButton.jsx
index 054c60cdcc..f8a55c9034 100644
--- a/packages/admin-panel/src/table/columnTypes/ExternalLinkButton.jsx
+++ b/packages/admin-panel/src/table/columnTypes/ExternalLinkButton.jsx
@@ -11,7 +11,14 @@ import { ColumnActionButton } from './ColumnActionButton';
import { makeSubstitutionsInString } from '../../utilities';
export const ExternalLinkButton = ({ actionConfig, row }) => {
- const fullUrl = makeSubstitutionsInString(actionConfig.url, row.original);
+ const getUrl = () => {
+ if (actionConfig.generateUrl) {
+ return actionConfig.generateUrl(row.original);
+ }
+ return makeSubstitutionsInString(actionConfig.url, row.original);
+ };
+ const fullUrl = getUrl();
+ if (!fullUrl) return null;
return (
Date: Thu, 11 Jul 2024 14:02:20 +1200
Subject: [PATCH 07/10] Default to DL and alphabetise the country codes
---
packages/admin-panel/src/routes/surveys/surveys.js | 3 ++-
packages/central-server/src/apiV2/surveys/GETSurveys.js | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/packages/admin-panel/src/routes/surveys/surveys.js b/packages/admin-panel/src/routes/surveys/surveys.js
index b5759a762c..5ec146840b 100644
--- a/packages/admin-panel/src/routes/surveys/surveys.js
+++ b/packages/admin-panel/src/routes/surveys/surveys.js
@@ -197,7 +197,8 @@ const SURVEY_COLUMNS = [
generateUrl: row => {
const { code, countryCodes } = row;
if (!countryCodes || countryCodes.length === 0) return null;
- return `${REACT_APP_DATATRAK_WEB_URL}/survey/${countryCodes[0]}/${code}/1`;
+ const countryCodeToUse = countryCodes.includes('DL') ? 'DL' : countryCodes[0];
+ return `${REACT_APP_DATATRAK_WEB_URL}/survey/${countryCodeToUse}/${code}/1`;
},
title: 'Preview survey',
},
diff --git a/packages/central-server/src/apiV2/surveys/GETSurveys.js b/packages/central-server/src/apiV2/surveys/GETSurveys.js
index e2b44588d3..5b77c04476 100644
--- a/packages/central-server/src/apiV2/surveys/GETSurveys.js
+++ b/packages/central-server/src/apiV2/surveys/GETSurveys.js
@@ -185,7 +185,7 @@ export class GETSurveys extends GETHandler {
`,
surveyIds,
);
- return Object.fromEntries(rows.map(row => [row.id, row.country_codes]));
+ return Object.fromEntries(rows.map(row => [row.id, row.country_codes.sort()]));
}
}
From 7fe920c3e820b37171b86f6d1867b735ddecf1ad Mon Sep 17 00:00:00 2001
From: alexd-bes <129009580+alexd-bes@users.noreply.github.com>
Date: Fri, 12 Jul 2024 13:15:19 +1200
Subject: [PATCH 08/10] Change tooltip text
---
packages/admin-panel/src/routes/surveys/surveys.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/admin-panel/src/routes/surveys/surveys.js b/packages/admin-panel/src/routes/surveys/surveys.js
index 5ec146840b..5944078d62 100644
--- a/packages/admin-panel/src/routes/surveys/surveys.js
+++ b/packages/admin-panel/src/routes/surveys/surveys.js
@@ -191,7 +191,7 @@ const SURVEY_COLUMNS = [
source: 'survey_group.name',
},
{
- Header: 'Preview',
+ Header: 'View',
type: 'externalLink',
actionConfig: {
generateUrl: row => {
@@ -200,7 +200,7 @@ const SURVEY_COLUMNS = [
const countryCodeToUse = countryCodes.includes('DL') ? 'DL' : countryCodes[0];
return `${REACT_APP_DATATRAK_WEB_URL}/survey/${countryCodeToUse}/${code}/1`;
},
- title: 'Preview survey',
+ title: 'View in Datatrak',
},
},
{
From fa4e8d0db04947a1718ad11ba0bd121c12b687cc Mon Sep 17 00:00:00 2001
From: alexd-bes <129009580+alexd-bes@users.noreply.github.com>
Date: Mon, 15 Jul 2024 11:28:12 +1200
Subject: [PATCH 09/10] Update copy
---
packages/admin-panel/src/routes/surveys/surveys.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/admin-panel/src/routes/surveys/surveys.js b/packages/admin-panel/src/routes/surveys/surveys.js
index 928f25aa0a..4076779239 100644
--- a/packages/admin-panel/src/routes/surveys/surveys.js
+++ b/packages/admin-panel/src/routes/surveys/surveys.js
@@ -205,7 +205,7 @@ const SURVEY_COLUMNS = [
const countryCodeToUse = countryCodes.includes('DL') ? 'DL' : countryCodes[0];
return `${REACT_APP_DATATRAK_WEB_URL}/survey/${countryCodeToUse}/${code}/1`;
},
- title: 'View in Datatrak',
+ title: 'View in DataTrak',
},
},
{
From 32c2bf0198e11df74598ea73ed3b23e63208850a Mon Sep 17 00:00:00 2001
From: alexd-bes <129009580+alexd-bes@users.noreply.github.com>
Date: Wed, 24 Jul 2024 09:16:28 +1200
Subject: [PATCH 10/10] Hide button for surveys with no countries
---
packages/admin-panel/src/routes/surveys/surveys.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/admin-panel/src/routes/surveys/surveys.js b/packages/admin-panel/src/routes/surveys/surveys.js
index 4076779239..eb52ca677b 100644
--- a/packages/admin-panel/src/routes/surveys/surveys.js
+++ b/packages/admin-panel/src/routes/surveys/surveys.js
@@ -201,7 +201,7 @@ const SURVEY_COLUMNS = [
actionConfig: {
generateUrl: row => {
const { code, countryCodes } = row;
- if (!countryCodes || countryCodes.length === 0) return null;
+ if (!countryCodes || !countryCodes.some(countryCode => !!countryCode)) return null;
const countryCodeToUse = countryCodes.includes('DL') ? 'DL' : countryCodes[0];
return `${REACT_APP_DATATRAK_WEB_URL}/survey/${countryCodeToUse}/${code}/1`;
},