Adds the list of the survey questions.

TODO:  Add delete functionality.  Add sort functionality.
Add preview functionality.
Toolbar will be built out with the other functionalities.
This commit is contained in:
Alex Corey 2020-03-03 22:33:28 -05:00
parent 7a3ece7fd2
commit edb3f6df55
7 changed files with 241 additions and 1 deletions

View File

@ -64,6 +64,16 @@ class JobTemplates extends SchedulesMixin(
params,
});
}
readScheduleList(id, params) {
return this.http.get(`${this.baseUrl}${id}/schedules/`, {
params,
});
}
readSurvey(id) {
return this.http.get(`${this.baseUrl}${id}/survey_spec/`);
}
}
export default JobTemplates;

View File

@ -15,6 +15,7 @@ import { ResourceAccessList } from '@components/ResourceAccessList';
import JobTemplateDetail from './JobTemplateDetail';
import JobTemplateEdit from './JobTemplateEdit';
import { JobTemplatesAPI, OrganizationsAPI } from '@api';
import SurveyList from './shared/SurveyList';
class Template extends Component {
constructor(props) {
@ -131,7 +132,7 @@ class Template extends Component {
},
{
name: i18n._(t`Survey`),
link: '/home',
link: `${match.url}/survey`,
}
);
@ -238,6 +239,12 @@ class Template extends Component {
)}
/>
)}
{template && (
<Route
path="/templates/:templateType/:id/survey"
render={() => <SurveyList template={template} />}
/>
)}
<Route
key="not-found"
path="*"

View File

@ -52,6 +52,7 @@ class Templates extends Component {
[`/templates/${template.type}/${template.id}/completed_jobs`]: i18n._(
t`Completed Jobs`
),
[`/templates/${template.type}/${template.id}/survey`]: i18n._(t`Survey`),
};
this.setState({ breadcrumbConfig });
};

View File

@ -0,0 +1,48 @@
import React, { useEffect, useCallback } from 'react';
import { withI18n } from '@lingui/react';
import useRequest from '@util/useRequest';
import ContentError from '@components/ContentError';
import ContentLoading from '@components/ContentLoading';
import { JobTemplatesAPI } from '@api';
import SurveyListItem from './SurveyListItem';
function SurveyList({ template }) {
const {
result: questions,
error: contentError,
isLoading,
request: fetchSurvey,
} = useRequest(
useCallback(async () => {
const {
data: { spec },
} = await JobTemplatesAPI.readSurvey(template.id);
return spec.map((s, index) => ({ ...s, id: index }));
}, [template.id])
);
useEffect(() => {
fetchSurvey();
}, [fetchSurvey]);
if (contentError) {
return <ContentError error={contentError} />;
}
if (isLoading) {
return <ContentLoading />;
}
return (
questions?.length > 0 &&
questions.map((question, index) => (
<SurveyListItem
key={question.id}
isLast={index === questions.length - 1}
isFirst={index === 0}
question={question}
/>
))
);
}
export default withI18n()(SurveyList);

View File

@ -0,0 +1,47 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
import SurveyList from './SurveyList';
import { JobTemplatesAPI } from '@api';
import mockJobTemplateData from './data.job_template.json';
jest.mock('@api/models/JobTemplates');
describe('<SurveyList />', () => {
beforeEach(() => {
JobTemplatesAPI.readSurvey.mockResolvedValue({
data: { spec: [{ question_name: 'Foo', type: 'text', default: 'Bar' }] },
});
});
test('expect component to mount successfully', async () => {
let wrapper;
await act(async () => {
wrapper = await mountWithContexts(
<SurveyList template={mockJobTemplateData} />
);
});
expect(wrapper.length).toBe(1);
});
test('expect api to be called to get survey', async () => {
let wrapper;
await act(async () => {
wrapper = await mountWithContexts(
<SurveyList template={mockJobTemplateData} />
);
});
expect(JobTemplatesAPI.readSurvey).toBeCalledWith(7);
wrapper.update();
expect(wrapper.find('SurveyListItem').length).toBe(1);
});
test('error in retrieving the survey throws an error', async () => {
JobTemplatesAPI.readSurvey.mockRejectedValue(new Error());
let wrapper;
await act(async () => {
wrapper = await mountWithContexts(
<SurveyList template={{ ...mockJobTemplateData, id: 'a' }} />
);
});
wrapper.update();
expect(wrapper.find('ContentError').length).toBe(1);
});
});

View File

@ -0,0 +1,80 @@
import React from 'react';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
import {
Button as _Button,
DataList,
DataListAction as _DataListAction,
DataListCheck,
DataListItemCells,
DataListItemRow,
DataListItem,
DataListCell,
Stack,
StackItem,
} from '@patternfly/react-core';
import { CaretDownIcon, CaretUpIcon } from '@patternfly/react-icons';
import styled from 'styled-components';
const DataListAction = styled(_DataListAction)`
margin-left: 0;
margin-right: 20px;
padding-top: 15px;
padding-bottom: 15px;
`;
const Button = styled(_Button)`
padding-top: 0;
padding-bottom: 0;
padding-left: 0;
`;
function SurveyListItem({ question, i18n, isLast, isFirst }) {
return (
<DataList aria-label={i18n._(t`Survey List`)}>
<DataListItem aria-labelledby={i18n._(t`Survey questions`)}>
<DataListItemRow>
<DataListAction
id="sortQuestions"
aria-labelledby={i18n._(t`Sort question order`)}
aria-label={i18n._(t`Sort question order`)}
>
<Stack>
<StackItem>
<Button
variant="plain"
aria-label={i18n._(t`move up`)}
isDisabled={isFirst}
>
<CaretUpIcon />
</Button>
</StackItem>
<StackItem>
<Button
variant="plain"
aria-label={i18n._(t`move down`)}
isDisabled={isLast}
>
<CaretDownIcon />
</Button>
</StackItem>
</Stack>
</DataListAction>
<DataListCheck checked={false} aria-labelledby="survey check" />
<DataListItemCells
dataListCells={[
<DataListCell key={question.question_name}>
{question.question_name}
</DataListCell>,
<DataListCell key={question.type}>{question.type}</DataListCell>,
<DataListCell key={question.default}>
{question.default}
</DataListCell>,
]}
/>
</DataListItemRow>
</DataListItem>
</DataList>
);
}
export default withI18n()(SurveyListItem);

View File

@ -0,0 +1,47 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
import SurveyListItem from './SurveyListItem';
describe('<SurveyListItem />', () => {
const item = { question_name: 'Foo', default: 'Bar', type: 'text' };
test('renders successfully', () => {
let wrapper;
act(() => {
wrapper = mountWithContexts(
<SurveyListItem question={item} isFirst={false} isLast={false} />
);
});
expect(wrapper.length).toBe(1);
});
test('fields are rendering properly', () => {
let wrapper;
act(() => {
wrapper = mountWithContexts(
<SurveyListItem question={item} isFirst={false} isLast={false} />
);
});
const moveUp = wrapper.find('Button[aria-label="move up"]');
const moveDown = wrapper.find('Button[aria-label="move down"]');
expect(moveUp.length).toBe(1);
expect(moveDown.length).toBe(1);
expect(wrapper.find('DataListCheck').length).toBe(1);
expect(wrapper.find('DataListCell').length).toBe(3);
});
test('move up and move down buttons are disabled', () => {
let wrapper;
act(() => {
wrapper = mountWithContexts(
<SurveyListItem question={item} isFirst isLast />
);
});
const moveUp = wrapper
.find('Button[aria-label="move up"]')
.prop('isDisabled');
const moveDown = wrapper
.find('Button[aria-label="move down"]')
.prop('isDisabled');
expect(moveUp).toBe(true);
expect(moveDown).toBe(true);
});
});