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

feat(datatrakWeb): RN-1336: Create a task workflow #5763

Merged
merged 62 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
346f3f9
Create button
alexd-bes Jun 20, 2024
beddfbe
Move modal to ui-components
alexd-bes Jun 20, 2024
6426a76
Move country selector to features folder
alexd-bes Jun 20, 2024
48d071b
Update country selector exports/imports
alexd-bes Jun 20, 2024
5d69b66
Update tupaia-pin.svg
alexd-bes Jun 20, 2024
446f2f9
Country selector on modal
alexd-bes Jun 20, 2024
b82596d
Move survey selector to features
alexd-bes Jun 20, 2024
c0cfc84
Move types
alexd-bes Jun 20, 2024
e19a287
Update survey list component to take care of fetching
alexd-bes Jun 20, 2024
694bf9a
Survey selector
alexd-bes Jun 20, 2024
8e54867
Move entity selector to features
alexd-bes Jun 20, 2024
57b534e
Fix types
alexd-bes Jun 20, 2024
3e564db
Entity selector
alexd-bes Jun 21, 2024
459ad7a
Styling entity selector
alexd-bes Jun 21, 2024
a103486
Merge branch 'rn-1335-tasks-dashboard' into rn-1336-create-task
alexd-bes Jun 21, 2024
0ab2903
Merge branch 'rn-1335-tasks-dashboard' into rn-1336-create-task
alexd-bes Jun 21, 2024
9b9a8af
Merge branch 'rn-1335-tasks-dashboard' into rn-1336-create-task
alexd-bes Jun 23, 2024
ec8f0fc
Due date
alexd-bes Jun 24, 2024
554d8fb
Merge branch 'rn-1335-tasks-dashboard' into rn-1336-create-task
alexd-bes Jun 24, 2024
1d68c15
Merge branch 'rn-1335-tasks-dashboard' into rn-1336-create-task
alexd-bes Jun 24, 2024
1a23d18
WIP
alexd-bes Jun 25, 2024
90a7f38
WIP
alexd-bes Jun 25, 2024
6d22957
assignee input
alexd-bes Jun 25, 2024
42bee49
Add loading state and save user id
alexd-bes Jun 25, 2024
8e7c924
Styling repeat scheduler
alexd-bes Jun 25, 2024
aed01e9
Comments placholder
alexd-bes Jun 25, 2024
c6e7e98
Styling
alexd-bes Jun 25, 2024
931a8cd
WIP
alexd-bes Jun 25, 2024
926805d
Create task route
alexd-bes Jul 5, 2024
4eaf58e
Create task workflow
alexd-bes Jul 5, 2024
a1459b4
Clear form when modal is reopened
alexd-bes Jul 5, 2024
7eb52c8
Merge branch 'rn-1335-tasks-dashboard' into rn-1336-create-task
alexd-bes Jul 5, 2024
b8a42d6
Update schemas.ts
alexd-bes Jul 5, 2024
9b89ff9
remove unused import
alexd-bes Jul 5, 2024
d8b9d64
Handle reset
alexd-bes Jul 5, 2024
176ac66
Fix datatrak tests
alexd-bes Jul 7, 2024
6ad35f9
Fix central server tests
alexd-bes Jul 7, 2024
e186d8f
Move modal to ui-components
alexd-bes Jul 7, 2024
62197cc
Merge branch 'rn-1336-modal-component' into rn-1336-create-task
alexd-bes Jul 7, 2024
8553b62
Merge branch 'rn-1335-tasks-dashboard' into rn-1336-modal-component
alexd-bes Jul 7, 2024
91d2cf1
Merge branch 'rn-1336-modal-component' into rn-1336-create-task
alexd-bes Jul 7, 2024
a9edba9
Remove unused import
alexd-bes Jul 7, 2024
ff82d5c
Remove duplicate file
alexd-bes Jul 7, 2024
18f6db8
Fix build
alexd-bes Jul 7, 2024
8a07448
Merge branch 'rn-1336-modal-component' into rn-1336-create-task
alexd-bes Jul 7, 2024
bc85374
Fix tests
alexd-bes Jul 7, 2024
f3ad9bd
Update packages/central-server/src/apiV2/utilities/constructNewRecord…
alexd-bes Jul 8, 2024
416339f
Fix error messages
alexd-bes Jul 8, 2024
cce3119
Handle search term in the BE
alexd-bes Jul 9, 2024
914fc10
Merge branch 'rn-1335-tasks-dashboard' into rn-1336-create-task
alexd-bes Jul 9, 2024
365a3ce
Fix timezone issue
alexd-bes Jul 9, 2024
2b6bdb2
Fix date formatting of filter
alexd-bes Jul 9, 2024
c711bef
remove unused variable
alexd-bes Jul 9, 2024
d9c6948
Fix casing
alexd-bes Jul 10, 2024
6671628
Default to showing countries if no primary entity question
alexd-bes Jul 10, 2024
e3a0212
Update AssigneeInput.tsx
alexd-bes Jul 10, 2024
6705438
Show loader when loading project and countries
alexd-bes Jul 10, 2024
a5cf0b1
Fix copy
alexd-bes Jul 10, 2024
f1662ba
Exclude internal users
alexd-bes Jul 10, 2024
16bbad6
Fix types
alexd-bes Jul 10, 2024
c2271d3
Change colour of icon in entity list
alexd-bes Jul 10, 2024
9776683
Merge branch 'rn-1335-tasks-dashboard' into rn-1336-create-task
alexd-bes Jul 11, 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
Expand Up @@ -7,7 +7,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Dialog } from '@material-ui/core';
import styled from 'styled-components';
import { ModalHeader } from '../../../../widgets';
import { ModalHeader } from '@tupaia/ui-components';

const Wrapper = styled.div`
height: 80vh;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
*/
import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
import { Button, Dialog } from '@tupaia/ui-components';
import {
Button,
Dialog,
ModalContentProvider,
ModalFooter,
ModalHeader,
} from '@tupaia/ui-components';
import { DashboardItemMetadataForm } from '../DashboardItem';
import { MapOverlayMetadataForm } from '../MapOverlay';
import { DASHBOARD_ITEM_OR_MAP_OVERLAY_PARAM } from '../../constants';
import { ModalContentProvider, ModalFooter, ModalHeader } from '../../../widgets';

export const EditModal = () => {
const { dashboardItemOrMapOverlay } = useParams();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Link as RouterLink, useParams } from 'react-router-dom';
import Typography from '@material-ui/core/Typography';
import { Modal, ModalCenteredContent } from '@tupaia/ui-components';
import { DASHBOARD_ITEM_OR_MAP_OVERLAY_PARAM, MODAL_STATUS } from '../../constants';
import { useVisualisationContext, useVizConfigContext } from '../../context';
import { useSaveDashboardVisualisation, useSaveMapOverlayVisualisation } from '../../api';
import { useVizBuilderBasePath } from '../../utils';
import { Modal, ModalCenteredContent } from '../../../widgets';

const Heading = styled(Typography).attrs({
variant: 'h3',
Expand Down
2 changes: 1 addition & 1 deletion packages/admin-panel/src/editor/EditModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Modal } from '@tupaia/ui-components';
import { dismissEditor } from './actions';
import { UsedBy } from '../usedBy/UsedBy';
import { Modal } from '../widgets';
import { useEditFiles } from './useEditFiles';
import { FieldsEditor } from './FieldsEditor';
import { withConnectedEditor } from './withConnectedEditor';
Expand Down
2 changes: 1 addition & 1 deletion packages/admin-panel/src/importExport/ExportModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Modal } from '../widgets';
import { Modal } from '@tupaia/ui-components';
import { useApiContext } from '../utilities/ApiProvider';
import { ActionButton } from '../editor';
import { ExportIcon } from '../icons';
Expand Down
4 changes: 2 additions & 2 deletions packages/admin-panel/src/importExport/ImportModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import React, { useState } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { FileUploadField } from '@tupaia/ui-components';
import { InputField, Modal } from '../widgets';
import { FileUploadField, Modal } from '@tupaia/ui-components';
import { InputField } from '../widgets';
import { useApiContext } from '../utilities/ApiProvider';
import { DATA_CHANGE_ERROR, DATA_CHANGE_REQUEST, DATA_CHANGE_SUCCESS } from '../table/constants';
import { checkVisibilityCriteriaAreMet, labelToId } from '../utilities';
Expand Down
9 changes: 1 addition & 8 deletions packages/admin-panel/src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,7 @@ export { PrivateRoute } from './authentication';
export { getHasBESAdminAccess } from './utilities/getHasBESAdminAccess';
export * from './pages/resources';
export { ReduxAutocomplete } from './autocomplete';
export {
IconButton,
ModalContentProvider,
Modal,
ModalFooter,
ModalHeader,
ModalCenteredContent,
} from './widgets';
export { IconButton } from './widgets';
export { AdminPanelDataProviders } from './utilities/AdminPanelProviders';
export { useApiContext } from './utilities/ApiProvider';
export { DataChangeAction, ActionButton } from './editor';
Expand Down
2 changes: 1 addition & 1 deletion packages/admin-panel/src/logsTable/LogsModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Modal } from '@tupaia/ui-components';
import { changeLogsTablePage, closeLogsModal } from './actions';
import { Modal } from '../widgets';
import { LogsTable } from './LogsTable';

export const LogsModalComponent = ({
Expand Down
2 changes: 1 addition & 1 deletion packages/admin-panel/src/qrCode/QrCodeModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Modal } from '@tupaia/ui-components';
import { QrCodeContainer } from './QrCodeContainer';
import { closeQrCodeModal } from './actions';
import { Modal } from '../widgets';

export const QrCodeModalComponent = ({ isOpen, onDismiss, qrCodeContents, humanReadableId }) => {
return (
Expand Down
4 changes: 2 additions & 2 deletions packages/admin-panel/src/surveyResponse/FileQuestionField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import React, { useState } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import generateId from 'uuid/v1';
import { TextField } from '@tupaia/ui-components';
import { TextField, Modal } from '@tupaia/ui-components';
import { getUniqueFileNameParts } from '@tupaia/utils';
import EditIcon from '@material-ui/icons/Edit';
import DeleteIcon from '@material-ui/icons/Delete';
import ExportIcon from '@material-ui/icons/GetApp';
import { FileUploadField } from '../widgets/InputField/FileUploadField';
import { IconButton, Modal } from '../widgets';
import { IconButton } from '../widgets';
import { useApiContext } from '../utilities/ApiProvider';

const Container = styled.div`
Expand Down
3 changes: 1 addition & 2 deletions packages/admin-panel/src/surveyResponse/Form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@

import React, { useState, useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Button } from '@tupaia/ui-components';
import { Button, ModalContentProvider, ModalFooter } from '@tupaia/ui-components';
import { Divider } from '@material-ui/core';
import { useGetExistingData } from './useGetExistingData';
import { ModalContentProvider, ModalFooter } from '../widgets';
import { useResubmitSurveyResponse } from '../api/mutations/useResubmitSurveyResponse';
import { MODAL_STATUS } from './constants';
import { SurveyScreens } from './SurveyScreens';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Dialog } from '@tupaia/ui-components';
import { Dialog, ModalHeader } from '@tupaia/ui-components';
import { closeResubmitSurveyModal, onAfterMutate as onAfterMutateAction } from './actions';
import { Form } from './Form';
import { ModalHeader } from '../widgets';

export const ResubmitSurveyResponseModalComponent = ({
isOpen,
Expand Down
2 changes: 1 addition & 1 deletion packages/admin-panel/src/widgets/ConfirmDeleteModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import Typography from '@material-ui/core/Typography';
import styled from 'styled-components';
import { Modal, ModalCenteredContent } from './Modal';
import { Modal, ModalCenteredContent } from '@tupaia/ui-components';

const Heading = styled(Typography).attrs({
variant: 'h3',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import {
Typography,
} from '@material-ui/core';
import { Search } from '@material-ui/icons';
import { InputLabel } from '@tupaia/ui-components';
import { InputLabel, useDebounce } from '@tupaia/ui-components';
import { get } from '../../VizBuilderApp/api/api';
import { Checkbox } from '../Checkbox';
import { convertSearchTermToFilter, useDebounce } from '../../utilities';
import { convertSearchTermToFilter } from '../../utilities';

const useOptions = (
endpoint,
Expand Down
7 changes: 0 additions & 7 deletions packages/admin-panel/src/widgets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ export { InputField, JsonEditorInputField } from './InputField';
export { Tabs } from './Tabs';
export { PageHeader } from './PageHeader';
export { PageBody, Footer } from '../layout';
export {
ModalContentProvider,
Modal,
ModalFooter,
ModalHeader,
ModalCenteredContent,
} from './Modal';
export { JsonEditor, JsonTreeEditor } from './JsonEditor';
export { SecondaryNavbar } from '../layout/navigation/SecondaryNavbar';
export { ConfirmDeleteModal } from './ConfirmDeleteModal';
Original file line number Diff line number Diff line change
Expand Up @@ -446,26 +446,45 @@ export const constructForSingle = (models, recordType) => {
survey_id: [constructRecordExistsWithId(models.survey)],
assignee_id: [constructIsEmptyOr(constructRecordExistsWithId(models.user))],
due_date: [
(value, { status }) => {
if (status !== 'repeating' && !value) {
throw new Error('Due date is required for non-recurring tasks');
(value, { repeat_schedule: repeatSchedule }) => {
if (repeatSchedule) {
if (value) {
throw new Error('Non-recurring tasks must not have a due date');
tcaiger marked this conversation as resolved.
Show resolved Hide resolved
}
return true;
}
if (!value) throw new Error('Due date is required for non-recurring tasks');
return true;
},
],
repeat_schedule: [
(value, { status }) => {
if (status === 'repeating' && !value) {
throw new Error('Repeat frequency is required for recurring tasks');
(value, { due_date: dueDate }) => {
// If the task has a due date, the repeat schedule is empty
if (dueDate) {
if (value) {
throw new Error('Non-recurring tasks cannot have a repeat schedule');
alexd-bes marked this conversation as resolved.
Show resolved Hide resolved
}
return true;
}

if (!value) {
throw new Error('Repeat schedule is required for recurring tasks');
}
return true;
},
],
status: [
(value, { repeat_schedule: repeatSchedule }) => {
if (repeatSchedule) return true;
// If the task is recurring, the status is empty
if (repeatSchedule) {
if (value) {
throw new Error('Recurring tasks cannot have a status');
}
return true;
}

if (!value) {
throw new Error('Status is required');
throw new Error('Status is required for non-recurring tasks');
}
return true;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('Permissions checker for CreateTask', async () => {

const BASE_TASK = {
assignee_id: assignee.id,
repeat_schedule: '{}',
repeat_schedule: null,
due_date: new Date('2021-12-31'),
status: 'to_do',
};
Expand Down
33 changes: 26 additions & 7 deletions packages/database/src/DatabaseModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ export class DatabaseModel {
if (!this.fieldNames) {
const schema = await this.fetchSchema();

this.fieldNames = Object.keys(schema);
const customColumnSelectors = this.customColumnSelectors || {};

this.fieldNames = [
...new Set([...Object.keys(schema), ...Object.keys(customColumnSelectors)]),
];
}
return this.fieldNames;
}
Expand Down Expand Up @@ -120,11 +124,12 @@ export class DatabaseModel {
// Alias field names to the table to prevent errors when joining other tables
// with same column names.
const fieldNames = await this.fetchFieldNames();

return fieldNames.map(fieldName => {
const qualifiedName = this.fullyQualifyColumn(fieldName);
const customSelector = this.customColumnSelectors && this.customColumnSelectors[fieldName];
if (customSelector) {
return { [fieldName]: customSelector(qualifiedName) };
const customColumnSelector = this.getColumnSelector(fieldName, qualifiedName);
if (customColumnSelector) {
return { [fieldName]: customColumnSelector };
}
return qualifiedName;
});
Expand All @@ -148,6 +153,14 @@ export class DatabaseModel {
return { ...options, ...customQueryOptions };
}

getColumnSelector(fieldName, qualifiedName) {
const customSelector = this.customColumnSelectors && this.customColumnSelectors[fieldName];
if (customSelector) {
return customSelector(qualifiedName);
}
return null;
}

async getDbConditions(dbConditions = {}) {
const fieldNames = await this.fetchFieldNames();
const fullyQualifiedConditions = {};
Expand All @@ -162,9 +175,15 @@ export class DatabaseModel {
// Don't touch RAW conditions
fullyQualifiedConditions[field] = value;
} else {
const fullyQualifiedField = fieldNames.includes(field)
? this.fullyQualifyColumn(field)
: field;
const qualifiedName = this.fullyQualifyColumn(field);
const customSelector = this.getColumnSelector(field, qualifiedName);
let fieldSelector = qualifiedName;
// if there is a custom selector, and it is a string, use it as the field selector. In some cases it will be an object, e.g. `castAs: 'text'` which is used to cast the field to a specific type, but this is not used as the field selector as an error will be thrown.
if (customSelector && typeof customSelector === 'string') {
fieldSelector = customSelector;
}

const fullyQualifiedField = fieldNames.includes(field) ? fieldSelector : field;
fullyQualifiedConditions[fullyQualifiedField] = value;
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/database/src/TupaiaDatabase.js
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ export class TupaiaDatabase {
*/
function buildQuery(connection, queryConfig, where = {}, options = {}) {
const { recordType, queryMethod, queryMethodParameter } = queryConfig;

let query = connection(recordType); // Query starts as just the table, but will be built up

// If an innerQuery is defined, make the outer query wrap it
Expand Down Expand Up @@ -676,6 +677,7 @@ function addWhereClause(connection, baseQuery, where) {
}

const columnKey = getColSelector(connection, key);

const columnSelector = castAs ? connection.raw(`??::${castAs}`, [columnKey]) : columnKey;

const { args = [comparator, sanitizeComparisonValue(comparator, comparisonValue)] } = value;
Expand Down Expand Up @@ -740,6 +742,7 @@ function getColSelector(connection, inputColStr) {
return connection.raw(`COALESCE(${identifiers})`, bindings);
}
const casePattern = /^CASE/;

if (casePattern.test(inputColStr)) {
return connection.raw(inputColStr);
}
Expand Down
11 changes: 11 additions & 0 deletions packages/database/src/modelClasses/PermissionGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ export class PermissionGroupRecord extends DatabaseRecord {
permissionGroupTree.map(treeItemFields => this.model.generateInstance(treeItemFields)),
);
}

async getAncestors() {
const permissionGroupTree = await this.model.database.findWithParents(
this.constructor.databaseRecord,
this.id,
);

return Promise.all(
permissionGroupTree.map(treeItemFields => this.model.generateInstance(treeItemFields)),
);
}
}

export class PermissionGroupModel extends DatabaseModel {
Expand Down
8 changes: 8 additions & 0 deletions packages/database/src/modelClasses/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ export class UserModel extends DatabaseModel {
return user;
}

customColumnSelectors = {
full_name: () =>
`CASE
WHEN last_name IS NULL THEN first_name
ELSE first_name || ' ' || last_name
END`,
};

emailVerifiedStatuses = {
UNVERIFIED: 'unverified',
VERIFIED: 'verified',
Expand Down
5 changes: 5 additions & 0 deletions packages/datatrak-web-server/examples.http
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,8 @@ content-type: {{contentType}}
### Get survey
GET {{host}}/surveys/TAR HTTP/1.1
content-type: {{contentType}}


### Get survey users
GET {{host}}/users/TAR HTTP/1.1
content-type: {{contentType}}
6 changes: 6 additions & 0 deletions packages/datatrak-web-server/src/app/createApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ import {
GenerateLoginTokenRequest,
TasksRequest,
TasksRoute,
SurveyUsersRequest,
SurveyUsersRoute,
CreateTaskRequest,
CreateTaskRoute,
} from '../routes';
import { attachAccessPolicy } from './middleware';

Expand Down Expand Up @@ -82,6 +86,8 @@ export async function createApp() {
.get<ActivityFeedRequest>('activityFeed', handleWith(ActivityFeedRoute))
.get<TasksRequest>('tasks', handleWith(TasksRoute))
.get<SingleSurveyResponseRequest>('surveyResponse/:id', handleWith(SingleSurveyResponseRoute))
.get<SurveyUsersRequest>('users/:surveyCode/:countryCode', handleWith(SurveyUsersRoute))
.post<CreateTaskRequest>('tasks', handleWith(CreateTaskRoute))
// Forward auth requests to web-config
.use('signup', forwardRequest(WEB_CONFIG_API_URL, { authHandlerProvider }))
.use('resendEmail', forwardRequest(WEB_CONFIG_API_URL, { authHandlerProvider }))
Expand Down
Loading