Skip to content

Commit

Permalink
feat: export of forms or submissions to a json file (#3896)
Browse files Browse the repository at this point in the history
* Error creating events by calendar if End Date before Start Date or End time is before Start time

* fix:Status service view includes extraneous top margin above heading

* fix: add and edit notification type cancel state

* fix:Save button in queue editor disables incorrectly on role changes

* feat:form UI schema code completion hints based on the data schema

* fix: scroll in form editor and help content.

* fix:icon accessibilty issue and web-component upgrade issue

* fix: Form -- All uppercase letter in Input label in schema, but in preview show input label in Pascal Case

* fix: mobile view bottom up lay out

* feat: export of forms or submissions to a json file

---------

Co-authored-by: Shuang chen <[email protected]>
Co-authored-by: Shuang chen <[email protected]>
Co-authored-by: athena-chen-chen <[email protected]>
  • Loading branch information
4 people authored Dec 23, 2024
1 parent f29fcc6 commit 3aa240d
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export const FeedbacksList = (): JSX.Element => {
</div>
)}
<GoAButton
type="secondary"
type="primary"
size="normal"
variant="normal"
onClick={exportToCsv}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useEffect, useState } from 'react';

import { useDispatch, useSelector } from 'react-redux';

import { RootState } from '@store/index';
import { GoAButton, GoADropdown, GoADropdownItem, GoAFormItem } from '@abgov/react-components-new';
import { getFormDefinitions, getExportFormInfo } from '@store/form/action';
import { FormDefinition } from '@store/form/model';

export const FormExport = (): JSX.Element => {
const dispatch = useDispatch();

const [selectedForm, setSelectedForm] = useState<FormDefinition>();
const [resourceType, setResourceType] = useState('');
const formDefinitions = useSelector((state: RootState) => state.form.definitions);

const formList: FormDefinition[] = Object.entries(formDefinitions).map(([, value]) => value);
const next = useSelector((state: RootState) => state.form.nextEntries);

const exportToCsv = () => {
if (selectedForm?.submissionRecords === true) {
dispatch(getExportFormInfo(selectedForm?.id, 'submissions'));
} else {
dispatch(getExportFormInfo(selectedForm.id, 'forms'));
}
};

useEffect(() => {
if (next) {
dispatch(getFormDefinitions(next));
}
}, [next === 'NTA=']);

return (
<section>
<div>
<GoAFormItem label="Form types">
<GoADropdown
name="formTypes"
value={selectedForm?.name}
onChange={(name, value: string) => {
const currentForm = formList.find((form) => form.name === value);
setSelectedForm(currentForm);
setResourceType(currentForm?.submissionRecords === true ? 'Submissions' : 'Forms');
}}
aria-label="form-selection-dropdown"
width="100%"
testId="form-selection-dropdown"
>
{formList.map((item) => (
<GoADropdownItem
name="formTypeList"
key={item?.name}
label={item?.name}
value={item?.name}
testId={`${item?.name}`}
/>
))}
</GoADropdown>
</GoAFormItem>
<br />
<GoAFormItem label="Records">
<h3>{resourceType}</h3>
</GoAFormItem>
<br />
<GoAButton
type="primary"
size="normal"
variant="normal"
onClick={exportToCsv}
testId="exportBtn"
disabled={!selectedForm || Object.keys(formDefinitions).length === 0}
>
Export
</GoAButton>
</div>
</section>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { selectFormAppHost } from '@store/form/selectors';
import { fetchDirectory } from '@store/directory/actions';
import { getFormDefinitions } from '@store/form/action';
import { useLocation } from 'react-router-dom';

import { FormExport } from './export/formExport';
const HelpLink = (): JSX.Element => {
const dispatch = useDispatch();
useEffect(() => {
Expand Down Expand Up @@ -91,6 +91,9 @@ export const Form: FunctionComponent = () => {
setActiveIndex={setActiveIndex}
/>
</Tab>
<Tab label="Export" data-testid="form-export">
<FormExport />
</Tab>
</Tabs>
</>
</Main>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface ServiceUrls {
taskServiceApiUrl?: string;
feedbackServiceUrl: string;
formServiceApiUrl: string;
exportServiceUrl: string;
}

export interface FeatureFlags {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export function* fetchConfig(): SagaIterator {
commentServiceApiUrl: entryMapping['comment-service'],
feedbackServiceUrl: entryMapping['feedback-service'],
formServiceApiUrl: data.serviceUrls.formServiceUrl,
exportServiceUrl: entryMapping['export-service'],
},
featureFlags: data.featureFlags,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
FETCH_FEEDBACK_METRICS_ACTION,
fetchFeedbackMetricsSuccess,
} from './actions';

import { getAccessToken } from '@store/tenant/sagas';
import { SagaIterator } from 'redux-saga';
import moment from 'moment';
Expand Down
27 changes: 27 additions & 0 deletions apps/tenant-management-webapp/src/app/store/form/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export const RESOLVE_DATA_SCHEMA_FAILED_ACTION = 'form/RESOLVE_DATA_SCHEMA_FAILE
export const FETCH_FORM_METRICS_ACTION = 'form/FETCH_FORM_METRICS_ACTION';
export const FETCH_FORM_METRICS_SUCCESS_ACTION = 'form/FETCH_FORM_METRICS_SUCCESS_ACTION';

export const EXPORT_FORM_INFO_ACTION = 'form/EXPORT_FORM_INFO_ACTION';
export const EXPORT_FORM_INFO_SUCCESS_ACTION = 'form/EXPORT_FORM_INFO_SUCCESS_ACTION';

export interface ClearFormDefinitions {
type: typeof CLEAR_FORM_DEFINITIONS_ACTION;
}
Expand All @@ -48,6 +51,17 @@ export interface FetchFormDefinitionsSuccessAction {
after: string;
}

export interface ExportFormInfoAction {
type: typeof EXPORT_FORM_INFO_ACTION;
id: string;
resource: string;
}

export interface ExportFormInfoSuccessAction {
type: typeof EXPORT_FORM_INFO_SUCCESS_ACTION;
payload: '';
}

export interface UpdateFormDefinitionsAction {
type: typeof UPDATE_FORM_DEFINITION_ACTION;
definition: FormDefinition;
Expand Down Expand Up @@ -149,6 +163,8 @@ export type FormActionTypes =
| ClearFormDefinitions
| FetchFormDefinitionsSuccessAction
| FetchFormDefinitionsAction
| ExportFormInfoAction
| ExportFormInfoSuccessAction
| DeleteFormDefinitionAction
| DeleteFormDefinitionSuccessAction
| UpdateFormDefinitionsAction
Expand Down Expand Up @@ -210,6 +226,17 @@ export const getFormDefinitionsSuccess = (
after,
});

export const getExportFormInfo = (id: string, resource: string): ExportFormInfoAction => ({
type: EXPORT_FORM_INFO_ACTION,
id,
resource,
});

export const getExportFormInfoSuccess = (): ExportFormInfoSuccessAction => ({
type: EXPORT_FORM_INFO_SUCCESS_ACTION,
payload: '',
});

export const deleteFormById = (id: string): DeleteFormByIDAction => ({
type: DELETE_FORM_BY_ID_ACTION,
id,
Expand Down
12 changes: 12 additions & 0 deletions apps/tenant-management-webapp/src/app/store/form/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ export const fetchFormDefinitionsApi = async (token: string, url: string): Promi
return res.data;
};

export const exportApi = async (
token: string,
url: string,
// eslint-disable-next-line
requestBody: any
): Promise<Record<string, FormDefinition>> => {
const res = await axios.post(url, requestBody, {
headers: { Authorization: `Bearer ${token}` },
});
return res.data;
};

export const fetchFormDefinitionApi = async (
token: string,
serviceUrl: string,
Expand Down
1 change: 1 addition & 0 deletions apps/tenant-management-webapp/src/app/store/form/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const defaultFormDefinition: FormDefinition = {

export interface FormState {
definitions: Record<string, FormDefinition>;

nextEntries: string;
editor: {
selectedId: string;
Expand Down
26 changes: 26 additions & 0 deletions apps/tenant-management-webapp/src/app/store/form/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
UpdateFormDefinitionsAction,
getFormDefinitionsSuccess,
updateFormDefinitionSuccess,
EXPORT_FORM_INFO_ACTION,
FETCH_FORM_DEFINITIONS_ACTION,
UPDATE_FORM_DEFINITION_ACTION,
DELETE_FORM_DEFINITION_ACTION,
Expand Down Expand Up @@ -44,6 +45,7 @@ import {
updateFormDefinitionApi,
deleteFormDefinitionApi,
fetchFormDefinitionApi,
exportApi,
} from './api';
import { FormDefinition } from './model';

Expand Down Expand Up @@ -88,6 +90,29 @@ export function* fetchFormDefinitions(payload): SagaIterator {
}
}

export function* exportFormInfo(payload): SagaIterator {
const exportBaseUrl: string = yield select((state: RootState) => state.config.serviceUrls?.exportServiceUrl);
const token: string = yield call(getAccessToken);
if (exportBaseUrl && token) {
try {
const url = `${exportBaseUrl}/export/v1/jobs`;
const requestBody = {
resourceId: `urn:ads:platform:form-service:v1:/${payload.resource}`,
format: 'json',
params: {
criteria: JSON.stringify({
definitionIdEquals: payload.id,
}),
},
filename: `Exports-${new Date().toISOString().replace(/[:.]/g, '-')}`,
};
yield call(exportApi, token, url, requestBody);
} catch (err) {
yield put(ErrorNotification({ error: err }));
}
}
}

const ensureRolesAreUniqueWithNoDuplicates = (definition: FormDefinition) => {
definition.applicantRoles = [...new Set(definition.applicantRoles)];
definition.clerkRoles = [...new Set(definition.clerkRoles)];
Expand Down Expand Up @@ -249,6 +274,7 @@ export function* fetchFormMetrics(): SagaIterator {

export function* watchFormSagas(): Generator {
yield takeEvery(FETCH_FORM_DEFINITIONS_ACTION, fetchFormDefinitions);
yield takeEvery(EXPORT_FORM_INFO_ACTION, exportFormInfo);
yield takeEvery(UPDATE_FORM_DEFINITION_ACTION, updateFormDefinition);
yield takeEvery(DELETE_FORM_DEFINITION_ACTION, deleteFormDefinition);
yield takeEvery(OPEN_EDITOR_FOR_DEFINITION_ACTION, openEditorForDefinition);
Expand Down

0 comments on commit 3aa240d

Please sign in to comment.