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`; },