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

create new tag #658

Merged
merged 14 commits into from
Dec 17, 2020
6 changes: 3 additions & 3 deletions frontend-project/src/models/global.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { notification } from 'antd';
import { ReactNode } from 'react';
import { IconType } from 'antd/lib/notification';

export interface GlobalModelState {
collapsed: boolean;
}

export const openNotificationWithIcon = (message: IconType, title: string, description: string) => {
notification[message]({ message: title, description });
export const openNotificationWithIcon = (message: IconType, description: ReactNode) => {
notification[message]({ message, description });
};

const GlobalModel = {
namespace: 'global',

state: {
collapsed: false,
},
Expand Down
59 changes: 59 additions & 0 deletions frontend-project/src/models/tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { create } from '@/services/tags';
import { Effect } from 'dva';
import { Reducer } from 'redux';
import { router } from 'umi';
import { openNotificationWithIcon } from '@/models/global';
import { formatMessage } from 'umi-plugin-react/locale';
import { Tag } from '@/services/definitions';

export interface TagModelState {
status?: number;
tag?: Tag;
}

export interface TagModelType {
namespace: string;
state: TagModelState;
effects: {
create: Effect;
};
reducers: {
changeTagStatus: Reducer<TagModelState>;
};
}
const TagState: TagModelState = { status: 1, tag: { name: '', id: 0 } };
const TagModel: TagModelType = {
namespace: 'tag',
state: TagState,
effects: {
*create({ payload }, { call, put }) {
try {
const response = yield call(create, payload);
yield put({
type: 'changeTagStatus',
payload: response,
});
openNotificationWithIcon(
'success',
formatMessage({ id: 'tags-new.page-create-notiffy-success' }) + response.data.id,
);
router.replace(`/tags/list`);
} catch (err) {
yield put({
type: 'changeTagStatus',
payload: err,
});
}
},
},
reducers: {
changeTagStatus(state, { payload }) {
return {
...state,
status: payload.response.status,
tag: payload.response.body,
};
},
},
};
export default TagModel;
30 changes: 29 additions & 1 deletion frontend-project/src/models/tags.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { fetchAll, fetchPage } from '@/services/tags';
import { fetchAll, fetchPage, create } from '@/services/tags';
import { Effect } from 'dva';
import { Reducer } from 'redux';
import { router } from 'umi';
import { Tag } from '@/services/definitions';
import { openNotificationWithIcon } from '@/models/global';
import { formatMessage } from 'umi-plugin-react/locale';

export type TagDefaultState = Tag[];

export interface TagModelType {
namespace: string;
state: Tag[];
effects: {
create: Effect;
fetchAll: Effect;
fetchPage: Effect;
};
Expand All @@ -23,6 +27,30 @@ const TagsModel: TagModelType = {
namespace: 'tags',
state: defaultTagsState,
effects: {
*create({ payload }, { call }) {
try {
const response = yield call(create, payload);
openNotificationWithIcon(
'success',
formatMessage({ id: 'tags-new.page-create-notiffy-success' }) + response.data.id,
);
router.replace(`/tags/list`);
} catch (err) {
if (err.response.status === 400 && err.response.body.name) {
err.response.body.name.forEach(message =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Czy możemy to zapisać generycznie, aby błędy w polach wyświetlały się przy polach formularzach, zamiast w powiadomieniu bez informacji jakiego pola dotyczy błąd?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ja nie widzę problemu, aby dopracować strukturę odpowiedzi błędu API, ale potrzebne wytyczne co do potrzeb.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ad-m W oknie logowania, mamy wyświetlanie problemu w formularzu (pomiedzy "Hasło logowania do konta" a polem z loginem ) I gdybym posiedział to jestem w stanie jeszcze to odwzorować.
Niestety nie wiem jak mogła by wyglądać struktura odpowiedzi z api aby przypisywać błąd do konkretnego pola w formularzu. Pozwolę sobie zawołać @kuskoman, czy jesteś w stanie coś tutaj zasugerować ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obecna odpowiedź API wskazuje konkretne pole, bo masz name. Ja proponuje starać się użyć obecnej odpowiedzi, a jak problem zostanie rozpracowany mocniej (więcej przypadków) to zmienimy strukturę API, aby uprościć implementacje w front-endzie. Wykonamy to przed wejściem z V2 na produkcje.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ad-m siedziałem trochę godzin nad zagadnieniem, próbowałem szukać pomocy na forach jednak nie udało mi się zrobić odpowiedzi według Twoich założeń. Dokładniej mam problem z przekazaniem odpowiedzi z Modelu do formularza, próbowałem odgapić to z elementu logowania bądź z Cases/New (ponieważ są tam zwracane kolekcje tagów/instytucji itd.) jednak nic z tego.
Odniosę się tutaj do reszty issue, chętnie się za nie wezmę gdy ktoś opracuje standard edycji/dodawania/usuwania ponieważ tak jak mówiłem na pierwszym call'u to moje początki z reactem i reduxem i dopiero poznaje mechanikę ich działania :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ja w zakresie Reacta nie mam zbytniego doświadczenia także, ale podglądając zamykając projekt w Vue, z którym mam zaproponowałem:

const handleSubmit = (value: Tag) => {
    // dispatch({
    //   type: 'tags/create',
    //   payload: { ...value },
    // });
    const response = {
      name: [`To pole jest wymagane ${Math.random()}.`]
    };
    form.setFields(Object
        .entries(response)
        .map(([name, errors]) => ({ name, errors }))
    );
};

Teraz pozostaje tylko rozważenie gdzie ta logika obsługi błędów będzie się znajdować, czy w komponencie strony (jak teraz wkleiłem), czy w modelu (jak teraz jest), czy w serwisie (jak mogłoby teoretycznie być), a następnie odpowiednie przekazanie danych. Proszę o komentarz @fervero i @mateuszmagulski , którzy mają większe doświadczenie w projekcie.

Jeżeli będziemy mieli ten element poprawnie rozwiązany to możemy znacznie więcej walidacji pchnąć na back-end (na początku nawet nie musimy tracić czasu na komunikat dla wymaganego pola, bo back-end to sprawdzi). W zespole obecnie nie widzę osoby, która po prostu może wyznaczyć standard, więc raczej nie unikniemy tego.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zrzut ekranu 2020-10-26 o 19 18 03

@ad-m Ale taka walidacja na próbę zapisu pustego pola to była...

Copy link
Member

@ad-m ad-m Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ale mój kod nie odnosi się wyłącznie do wymaganego pola :) Kod może przetwarzać także pozostałe błędy z back-endu (nieprawidłowy format, złą długość itd.). Poza tym jest gwarantowane, że będzie to spójne z back-endem. Akurat tym razem przypadek nam się powtórzył, ale patrz szerzej jako na standard.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tylko wąski zakres błędów jesteś w stanie sprawdzić po stronie front-endu.

Copy link
Contributor Author

@daxter44 daxter44 Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zrzut ekranu 2020-10-26 o 22 01 05

Dowiozłem to ! Po wysłaniu tag'a treść błędu dotycząca pola name jest w: props.createdTag.tag?.name

openNotificationWithIcon(
'error',
formatMessage({ id: 'tags-new.page-create-notiffy-error' }) + message,
),
);
} else {
openNotificationWithIcon(
'error',
formatMessage({ id: 'tags-new.page-create-notiffy-error' }) + err.response.body.detail,
);
}
}
},
*fetchAll(_, { call, put }) {
const response = yield call(fetchAll);
yield put({
Expand Down
30 changes: 19 additions & 11 deletions frontend-project/src/pages/tags/new/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { Button, Col, Card, Form, Input, Row } from 'antd';
import { connect } from 'dva';
import { AnyAction, Dispatch } from 'redux';
import React, { useEffect, FC } from 'react';
import { formatMessage, FormattedMessage } from 'umi-plugin-react/locale';
import { Tag } from '@/services/definitions';
import { TagModelState } from '@/models/tag';

interface TagNewFormProps {
name: String;
dispatch: Function;
export interface TagNewFormProps {
dispatch: Dispatch<AnyAction>;
submitting: boolean;
tagState: TagModelState;
}

const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
Expand All @@ -17,17 +20,22 @@ const layout = {
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
};

const TagNewForm: FC<TagNewFormProps> = () => {
const TagNewForm: FC<TagNewFormProps> = (props: TagNewFormProps) => {
const [form] = Form.useForm();

const onSubmit = () => {
form.submit();
const handleSubmit = (value: Tag) => {
const { dispatch } = props;
dispatch({
type: 'tag/create',
payload: { ...value },
});
};

form.setFields(Object.entries(props?.tagState.tag).map(([name, errors]) => ({ name, errors })));
useEffect(() => {}, []);

return (
<Form {...layout} form={form}>
<Form {...layout} form={form} onFinish={handleSubmit}>
<PageHeaderWrapper content={formatMessage({ id: 'tags-new.page-header-content' })}>
<Card bordered={false}>
<Row>
Expand All @@ -49,7 +57,7 @@ const TagNewForm: FC<TagNewFormProps> = () => {
<Row>
<Col span={16}>
<Form.Item {...tailLayout}>
<Button type="primary" onClick={onSubmit}>
<Button type="primary" htmlType="submit">
<FormattedMessage id="tags-new.form.save.label" />
</Button>
</Form.Item>
Expand All @@ -61,4 +69,4 @@ const TagNewForm: FC<TagNewFormProps> = () => {
);
};

export default connect(() => ({}))(TagNewForm);
export default connect(state => ({ tagState: (state as any).tag }))(TagNewForm);
6 changes: 4 additions & 2 deletions frontend-project/src/pages/tags/new/locales/en-US.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export default {
'tags-new.form.save.label': 'Save',
'tags-new.form.name.label': 'Name',
'tags-new.form.name.placeholder': "Case's name",
'tags-new.form.name.placeholder': "Tag's name",
'tags-new.form.name.required-error': 'Name is required',
'tags-new.page-header-content': 'You can add new channel here.',
'tags-new.page-header-content': 'You can add new tag here.',
'tags-new.page-create-notiffy-error': 'Save error:',
'tags-new.page-create-notiffy-success': 'Tag saved corectly ID:',
};
4 changes: 3 additions & 1 deletion frontend-project/src/pages/tags/new/locales/pl-PL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ export default {
'tags-new.form.name.label': 'Nazwa',
'tags-new.form.name.placeholder': 'Nazwa tagu',
'tags-new.form.name.required-error': 'Nazwa jest wymagana',
'tags-new.page-header-content': 'Możesz dodać tutaj nowy kanał.',
'tags-new.page-header-content': 'Możesz dodać tutaj nowy tag.',
'tags-new.page-create-notiffy-error': 'Zapis nieudany:',
'tags-new.page-create-notiffy-success': 'Zapis prawidłowy ID:',
};
4 changes: 3 additions & 1 deletion frontend-project/src/services/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ function fetchAllPages(page: Page) {
}
return page;
}

export async function fetchPage({
current,
pageSize,
Expand All @@ -45,3 +44,6 @@ export async function fetchAll() {
smallEodSDK.TagsApi();
return smallEodSDK.tagsList().then(page => fetchAllPages(page));
}
export async function create(value: Tag): Promise<Tag> {
return new smallEodSDK.TagsApi().tagsCreateWithHttpInfo(value);
}