Skip to content

Commit

Permalink
feat(admin) ajout de la page info (#1067)
Browse files Browse the repository at this point in the history
* feat(admin) ajout de la page info

* fix: doublons sur publier + lien contenus

* chore: fix ts

* fix: remove column cdtnId from queries

* fix: block type not displaying

* fix: graphic validation + order duplicate key

* chore: fix types

* fix: use mapping to create graphql request

* fix: issues with mapping and requests

* fix: issues with publish

* fix: migration order

* chore: clean

* fix: mapping issues

* chore: add TU

---------

Co-authored-by: Victor Zeinstra <[email protected]>
  • Loading branch information
m-maillot and Victor Zeinstra authored Oct 23, 2023
1 parent 8283562 commit bdb184c
Show file tree
Hide file tree
Showing 86 changed files with 3,626 additions and 32 deletions.
1 change: 1 addition & 0 deletions shared/graphql-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const client = createClient({
"x-hasura-admin-secret": HASURA_GRAPHQL_ADMIN_SECRET,
},
},
maskTypename: true,
requestPolicy: "network-only",
url: HASURA_GRAPHQL_ENDPOINT,
});
1 change: 1 addition & 0 deletions targets/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"@shared/types": "workspace:^",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/formidable": "^2.0.5",
"@types/jest": "^27.4.0",
"@types/jsonwebtoken": "^9.0.3",
Expand Down
7 changes: 4 additions & 3 deletions targets/frontend/src/components/documents/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ DocumentList.propTypes = {
};

const DocumentRow = function DocumentRow({
document: { cdtnId, source, title, isPublished, isAvailable },
document: { cdtnId, id, source, title, isPublished, isAvailable },
}) {
const [selectedItems, setSelectedItems] = useSelectionContext();
const updatePublishedRef = () => {
Expand Down Expand Up @@ -82,7 +82,7 @@ const DocumentRow = function DocumentRow({
</TableCell>
<TableCell>
<Link
href={sourceToRoute({ cdtnId, source })}
href={sourceToRoute({ cdtnId, id, source })}
passHref
shallow
style={{ textDecoration: "none" }}
Expand Down Expand Up @@ -117,11 +117,12 @@ DocumentRow.propTypes = {
}).isRequired,
};

export const sourceToRoute = ({ cdtnId, source }) => {
export const sourceToRoute = ({ cdtnId, id, source }) => {
switch (source) {
case SOURCES.THEMES:
return `/themes/edit/${cdtnId}`;
case SOURCES.EDITORIAL_CONTENT:
return `/informations/${id}`;
case SOURCES.HIGHLIGHTS:
case SOURCES.PREQUALIFIED:
return `/contenus/edit/${cdtnId}`;
Expand Down
32 changes: 32 additions & 0 deletions targets/frontend/src/components/forms/Checkbox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FormGroup, FormControlLabel, Checkbox } from "@mui/material";
import React, { PropsWithChildren } from "react";
import { Controller } from "react-hook-form";
import { CommonFormProps } from "../type";

export type FormCheckboxProps = PropsWithChildren<CommonFormProps>;
export const FormCheckbox = ({
name,
rules,
label,
control,
disabled,
}: FormCheckboxProps) => {
return (
<Controller
name={name}
control={control}
rules={rules}
render={({ field: { onChange, value } }) => {
return (
<FormGroup>
<FormControlLabel
control={<Checkbox onChange={onChange} checked={value} />}
label={label}
disabled={disabled}
/>
</FormGroup>
);
}}
/>
);
};
4 changes: 3 additions & 1 deletion targets/frontend/src/components/forms/RadioGroup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@ export const FormRadioGroup = ({
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
onChange(event.target.value)
}
name={name}
>
{options.map(({ label, value }) => (
<FormControlLabel
key={value}
value={value}
control={<Radio />}
control={<Radio id={`${name}.${value}`} />}
label={label}
disabled={disabled}
htmlFor={`${name}.${value}`}
/>
))}
</RadioGroup>
Expand Down
36 changes: 19 additions & 17 deletions targets/frontend/src/components/forms/TextField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type FormTextFieldProps = CommonFormProps & {
fullWidth?: boolean;
multiline?: boolean;
labelFixed?: boolean;
id?: string;
};

export const FormTextField = ({
Expand All @@ -32,23 +33,24 @@ export const FormTextField = ({
name={name}
control={control}
rules={rules}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextField
helperText={
error && error.type === "required" ? "Ce champ est requis" : null
}
size={size}
error={!!error}
onChange={onChange}
value={value}
fullWidth={fullWidth}
label={label}
variant="outlined"
multiline={multiline}
disabled={disabled}
InputLabelProps={labelFixed ? { shrink: true } : {}}
/>
)}
render={({ field: { onChange, value }, fieldState: { error } }) => {
return (
<TextField
helperText={error?.message}
size={size}
error={!!error}
onChange={onChange}
value={value}
fullWidth={fullWidth}
label={label}
variant="outlined"
multiline={multiline}
disabled={disabled}
InputLabelProps={labelFixed ? { shrink: true } : {}}
id={name}
/>
);
}}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const getGhostDocumentQuery = gql`
type
parent: a {
cdtn_id
initial_id
source
title
}
Expand All @@ -47,7 +48,12 @@ export const getGhostDocumentQuery = gql`
export type ParentRef = Pick<HasuraDocument, "cdtn_id" | "title" | "source">;
export type DocumentRef = Pick<
HasuraDocument,
"cdtn_id" | "title" | "source" | "is_available" | "is_published"
| "cdtn_id"
| "initial_id"
| "title"
| "source"
| "is_available"
| "is_published"
>;

export type GhostRelation = {
Expand Down
2 changes: 1 addition & 1 deletion targets/frontend/src/components/layout/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function Navigation() {
label: "Contenus",
},
{
href: "/contenus?source=information",
href: "/informations",
label: "Contenus éditoriaux",
},
{
Expand Down
34 changes: 34 additions & 0 deletions targets/frontend/src/lib/__tests__/mutationUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { getElementsToDelete } from "../mutationUtils";

describe("Fonction utilitaire getElementsToDelete", () => {
it("doit remonter pour un champs donné, la liste d'élément différent entre les 2 objets", () => {
const result = getElementsToDelete(
{
list: [
{
id: 1,
},
{
id: 2,
},
{
id: 3,
},
],
},
{
list: [
{
id: 1,
},
{
id: 3,
},
],
},
["list", "id"]
);
expect(result.length).toEqual(1);
expect(result[0]).toEqual(2);
});
});
74 changes: 74 additions & 0 deletions targets/frontend/src/lib/api/ApiClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Client } from "urql";
import { DocumentNode } from "graphql/index";
import { TypedDocumentNode } from "@graphql-typed-document-node/core";
import { OperationContext, OperationResult } from "@urql/core/dist/types/types";
import { client as gqlClient } from "@shared/graphql-client";

export class ApiClient {
client: Client;
hasuraGraphqlAdminSecret: string;
sessionVariables?: any;

constructor(client: Client, sessionVariables?: any) {
this.client = client;
this.hasuraGraphqlAdminSecret =
process.env.HASURA_GRAPHQL_ADMIN_SECRET ?? "admin1";
this.sessionVariables = sessionVariables;
}

public static build(sessionVariables: any = undefined): ApiClient {
return new ApiClient(gqlClient, sessionVariables);
}

async query<Data = any, Variables extends object = {}>(
query: DocumentNode | TypedDocumentNode<Data, Variables> | string,
variables?: Variables,
context?: Partial<OperationContext>
): Promise<OperationResult<Data, Variables>> {
let headers = context?.headers;
if (this.sessionVariables) {
headers = {
...headers,
...this.sessionVariables,
"x-hasura-admin-secret": this.hasuraGraphqlAdminSecret,
};
}
const result = await this.client
.query(query, variables, {
...context,
fetchOptions: () => ({
...context?.fetchOptions,
headers,
}),
})
.toPromise();

return result;
}

async mutation<Data = any, Variables extends object = {}>(
query: DocumentNode | TypedDocumentNode<Data, Variables> | string,
variables?: Variables,
context?: Partial<OperationContext>
): Promise<OperationResult<Data, Variables>> {
let headers = context?.headers;
if (this.sessionVariables) {
headers = {
...headers,
...this.sessionVariables,
"x-hasura-admin-secret": this.hasuraGraphqlAdminSecret,
};
}
const result = await this.client
.mutation(query, variables, {
...context,
fetchOptions: () => ({
...context?.fetchOptions,
headers,
}),
})
.toPromise();

return result;
}
}
29 changes: 29 additions & 0 deletions targets/frontend/src/lib/api/ApiErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
interface ErrorWithCause<T> {
name: T;
message: string;
cause: any;
}

export class ErrorBase<T extends string> extends Error {
name: T;
message: string;
cause: any;

constructor(error: ErrorWithCause<T>) {
super();
this.name = error.name;
this.message = error.message;
this.cause = error.cause;
}
}

export class NotFoundError extends ErrorBase<"NOT_FOUND"> {}

export const DEFAULT_ERROR_500_MESSAGE =
"Internal server error during fetching data";

export class InvalidQueryError extends ErrorBase<"INVALID_QUERY"> {
constructor(message: string, cause: any) {
super({ name: "INVALID_QUERY", message, cause });
}
}
2 changes: 2 additions & 0 deletions targets/frontend/src/lib/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./ApiClient";
export * from "./ApiErrors";
9 changes: 0 additions & 9 deletions targets/frontend/src/lib/apiError.js

This file was deleted.

15 changes: 15 additions & 0 deletions targets/frontend/src/lib/apiError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { NextApiResponse } from "next";
import { Boom } from "@hapi/boom";

export function createErrorFor(res: NextApiResponse) {
return function toError({ output: { statusCode, payload } }: Boom) {
res.status(statusCode).json(payload);
};
}

export function serverError(
res: NextApiResponse,
{ output: { statusCode, payload } }: Boom
) {
return res.status(statusCode).json(payload);
}
26 changes: 26 additions & 0 deletions targets/frontend/src/lib/mutationUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const getElementsByPath = (obj: any, path: string[]): string[] => {
const clonedKeys = [...path];
const key = clonedKeys.shift();
if (!key) return [];
const objToParse = obj[key];
if (!clonedKeys.length && objToParse && typeof objToParse !== "object") {
return [objToParse];
} else if (Array.isArray(objToParse)) {
return objToParse.reduce((arr, item) => {
return arr.concat(getElementsByPath(item, clonedKeys));
}, []);
} else if (typeof objToParse === "object") {
return getElementsByPath(objToParse, clonedKeys);
}
return [];
};

export const getElementsToDelete = (
oldObj: any,
newObj: any,
keys: string[]
) => {
const oldIds = getElementsByPath(oldObj, keys);
const newIds = getElementsByPath(newObj, keys);
return oldIds.filter((el) => newIds.indexOf(el) === -1);
};
Loading

0 comments on commit bdb184c

Please sign in to comment.