Merge pull request #7208 from nixocio/ui_issue_7016

Add error feedback in Preview Step

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-06-04 19:04:43 +00:00
committed by GitHub
7 changed files with 60 additions and 26 deletions

View File

@@ -1,11 +1,29 @@
import React from 'react'; import React, { Fragment } from 'react';
import styled from 'styled-components';
import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons';
import { Tooltip } from '@patternfly/react-core';
import { t } from '@lingui/macro';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { withI18n } from '@lingui/react';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import PromptDetail from '../../PromptDetail';
import mergeExtraVars, { maskPasswords } from '../mergeExtraVars'; import mergeExtraVars, { maskPasswords } from '../mergeExtraVars';
import getSurveyValues from '../getSurveyValues'; import getSurveyValues from '../getSurveyValues';
import PromptDetail from '../../PromptDetail';
function PreviewStep({ resource, config, survey, formErrors }) { const ExclamationCircleIcon = styled(PFExclamationCircleIcon)`
margin-left: 10px;
margin-top: -2px;
`;
const ErrorMessageWrapper = styled.div`
align-items: center;
color: var(--pf-global--danger-color--200);
display: flex;
font-weight: var(--pf-global--FontWeight--bold);
margin-bottom: 10px;
`;
function PreviewStep({ resource, config, survey, formErrors, i18n }) {
const { values } = useFormikContext(); const { values } = useFormikContext();
const surveyValues = getSurveyValues(values); const surveyValues = getSurveyValues(values);
@@ -29,21 +47,26 @@ function PreviewStep({ resource, config, survey, formErrors }) {
} }
return ( return (
<> <Fragment>
{formErrors.length > 0 && (
<ErrorMessageWrapper>
{i18n._(t`Some of the previous step(s) have errors`)}
<Tooltip
position="right"
content={i18n._(t`See errors on the left`)}
trigger="click mouseenter focus"
>
<ExclamationCircleIcon />
</Tooltip>
</ErrorMessageWrapper>
)}
<PromptDetail <PromptDetail
resource={resource} resource={resource}
launchConfig={config} launchConfig={config}
overrides={overrides} overrides={overrides}
/> />
{formErrors && ( </Fragment>
<ul css="color: red">
{Object.keys(formErrors).map(
field => `${field}: ${formErrors[field]}`
)}
</ul>
)}
</>
); );
} }
export default PreviewStep; export default withI18n()(PreviewStep);

View File

@@ -24,6 +24,10 @@ const survey = {
], ],
}; };
const formErrors = {
inventory: 'An inventory must be selected',
};
describe('PreviewStep', () => { describe('PreviewStep', () => {
test('should render PromptDetail', async () => { test('should render PromptDetail', async () => {
let wrapper; let wrapper;
@@ -37,6 +41,7 @@ describe('PreviewStep', () => {
survey_enabled: true, survey_enabled: true,
}} }}
survey={survey} survey={survey}
formErrors={formErrors}
/> />
</Formik> </Formik>
); );
@@ -62,6 +67,7 @@ describe('PreviewStep', () => {
config={{ config={{
ask_limit_on_launch: true, ask_limit_on_launch: true,
}} }}
formErrors={formErrors}
/> />
</Formik> </Formik>
); );
@@ -85,6 +91,7 @@ describe('PreviewStep', () => {
config={{ config={{
ask_variables_on_launch: true, ask_variables_on_launch: true,
}} }}
formErrors={formErrors}
/> />
</Formik> </Formik>
); );

View File

@@ -19,7 +19,8 @@ export default function useCredentialsStep(
initialValues: getInitialValues(config, resource), initialValues: getInitialValues(config, resource),
validate, validate,
isReady: true, isReady: true,
error: null, contentError: null,
formError: null,
setTouched: setFieldsTouched => { setTouched: setFieldsTouched => {
setFieldsTouched({ setFieldsTouched({
credentials: true, credentials: true,

View File

@@ -27,7 +27,8 @@ export default function useInventoryStep(config, resource, visitedSteps, i18n) {
initialValues: getInitialValues(config, resource), initialValues: getInitialValues(config, resource),
validate, validate,
isReady: true, isReady: true,
error: null, contentError: null,
formError: stepErrors,
setTouched: setFieldsTouched => { setTouched: setFieldsTouched => {
setFieldsTouched({ setFieldsTouched({
inventory: true, inventory: true,

View File

@@ -24,7 +24,8 @@ export default function useOtherPrompt(config, resource, visitedSteps, i18n) {
initialValues: getInitialValues(config, resource), initialValues: getInitialValues(config, resource),
validate, validate,
isReady: true, isReady: true,
error: null, contentError: null,
formError: stepErrors,
setTouched: setFieldsTouched => { setTouched: setFieldsTouched => {
setFieldsTouched({ setFieldsTouched({
job_type: true, job_type: true,

View File

@@ -54,7 +54,8 @@ export default function useSurveyStep(config, resource, visitedSteps, i18n) {
validate, validate,
survey, survey,
isReady: !isLoading && !!survey, isReady: !isLoading && !!survey,
error, contentError: error,
formError: stepErrors,
setTouched: setFieldsTouched => { setTouched: setFieldsTouched => {
if (!survey || !survey.spec) { if (!survey || !survey.spec) {
return; return;

View File

@@ -13,14 +13,13 @@ export default function useSteps(config, resource, i18n) {
useOtherPromptsStep(config, resource, visited, i18n), useOtherPromptsStep(config, resource, visited, i18n),
useSurveyStep(config, resource, visited, i18n), useSurveyStep(config, resource, visited, i18n),
]; ];
const formErrorsContent = steps
.filter(s => s?.formError && Object.keys(s.formError).length > 0)
.map(({ formError }) => formError);
steps.push( steps.push(
usePreviewStep( usePreviewStep(config, resource, steps[3].survey, formErrorsContent, i18n)
config,
resource,
steps[3].survey,
{}, // TODO: formErrors ?
i18n
)
); );
const pfSteps = steps.map(s => s.step).filter(s => s != null); const pfSteps = steps.map(s => s.step).filter(s => s != null);
@@ -31,8 +30,9 @@ export default function useSteps(config, resource, i18n) {
}; };
}, {}); }, {});
const isReady = !steps.some(s => !s.isReady); const isReady = !steps.some(s => !s.isReady);
const stepWithError = steps.find(s => s.error);
const contentError = stepWithError ? stepWithError.error : null; const stepWithError = steps.find(s => s.contentError);
const contentError = stepWithError ? stepWithError.contentError : null;
const validate = values => { const validate = values => {
const errors = steps.reduce((acc, cur) => { const errors = steps.reduce((acc, cur) => {