From 4aca1458be9c280df65e9d106970b49c7d32d189 Mon Sep 17 00:00:00 2001 From: Deepak Jain Date: Thu, 30 Jul 2020 15:29:40 +0530 Subject: [PATCH] INFRA-447| Deepak Jain| creating api client wrapper --- package.json | 1 + src/components/layout/Header.tsx | 22 +- src/components/manifest/Form.tsx | 145 ++++++------ src/components/manifest/SchemaForm.tsx | 212 ++++++++++-------- .../manifest/ShowDeploymentManifest.tsx | 82 ++++--- src/components/manifest/ShowJson.tsx | 39 ++-- src/helper/api-client.ts | 36 +++ yarn.lock | 5 + 8 files changed, 309 insertions(+), 233 deletions(-) create mode 100644 src/helper/api-client.ts diff --git a/package.json b/package.json index 922f892..6f1f2e9 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "eject": "react-scripts eject" }, "dependencies": { + "js-cookie": "^2.2.1", "@jsonforms/core": "^2.4.0", "@jsonforms/material-renderers": "^2.4.0", "@jsonforms/react": "^2.4.0", diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index c5b1167..41541be 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -13,6 +13,7 @@ import { Link } from "react-router-dom"; import { store } from "../../store/store"; import { withCookies } from "react-cookie"; import { LinearProgress } from "@material-ui/core"; +import { httpClient } from "../../helper/api-client"; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -26,11 +27,12 @@ const useStyles = makeStyles((theme: Theme) => flexGrow: 1, }, progressBar: { - width: '100%', - '& > * + *': { + width: "100%", + "& > * + *": { marginTop: theme.spacing(1), - } - }}) + }, + }, + }) ); function MenuAppBar(props: any) { @@ -57,10 +59,8 @@ function MenuAppBar(props: any) { dispatch({ type: "setAuthentication", value: true }); }; const handleLogout = async () => { - fetch("/api/logout", { + httpClient("/api/logout", { method: "POST", - credentials: "include", - headers: { "X-XSRF-TOKEN": csrfToken }, }).then((response) => { if (response.ok) { dispatch({ type: "setAuthentication", value: false }); @@ -161,7 +161,13 @@ function MenuAppBar(props: any) { )} - {props.loading ?
: <>} + {props.loading ? ( +
+ +
+ ) : ( + <> + )} ); } diff --git a/src/components/manifest/Form.tsx b/src/components/manifest/Form.tsx index 3416f3b..50d4df3 100644 --- a/src/components/manifest/Form.tsx +++ b/src/components/manifest/Form.tsx @@ -5,88 +5,66 @@ import { Grid } from "@material-ui/core"; import { Alert } from "@material-ui/lab"; import { withCookies } from "react-cookie"; import SchemaForm from "./SchemaForm"; -import { toast } from 'react-toastify'; +import { toast } from "react-toastify"; import ConfirmationButton from "../common/ConfirmationButton"; import _ from "lodash"; - +import { httpClient } from "../../helper/api-client"; const formatDisplayData = (standaloneData) => { return JSON.stringify(standaloneData, null, 2); }; -const withError = (error: Object, component: React.ReactElement): React.ReactElement => { +const withError = ( + error: Object, + component: React.ReactElement +): React.ReactElement => { if (_.isEmpty(error)) { - return component + return component; } else { - return {formatDisplayData(error)} + return {formatDisplayData(error)}; } -} +}; const Form = (props: any) => { const { cookies } = props; const standaloneData = useRef({}); const [formInitData, setFormInitData] = useState({}); - const [loading, setLoading] = useState(true) - const [fetchError, setFetchError] = useState({}) + const [loading, setLoading] = useState(true); + const [fetchError, setFetchError] = useState({}); const manifestIdParam = props?.match?.params?.manifestId; const [csrfToken] = useState(cookies.get("XSRF-TOKEN")); const [errorData, setErrorData] = useState([]); const getManifestData = async (manifestId: number): Promise => { console.log(`Fetching manifest id ${manifestId}`); - return fetch(`/api/manifest/${manifestId}`, { - method: "GET", - credentials: "include", - headers: { - "X-XSRF-TOKEN": props.csrfToken, - Accept: "application/json", - "Content-Type": "application/json", - }, - }) + return httpClient(`/api/manifest/${manifestId}`); }; const getManifestDefaults = async (): Promise => { - return fetch(`/api/manifest/defaults`, { - method: "GET", - credentials: "include", - headers: { - "X-XSRF-TOKEN": props.csrfToken, - Accept: "application/json", - "Content-Type": "application/json", - }, - }) + return httpClient(`/api/manifest/defaults`); }; const getCopy = async (manifestId: number): Promise => { console.log(`Fetching copy of manifest id ${manifestId}`); - return fetch(`/api/manifest/${manifestId}/copy`, { - method: "GET", - credentials: "include", - headers: { - "X-XSRF-TOKEN": props.csrfToken, - Accept: "application/json", - "Content-Type": "application/json", - }, - }) + return httpClient(`/api/manifest/${manifestId}/copy`); }; useEffect(() => { const queryParams = new URLSearchParams(props.location.search); const isCopy = String(props.location.search).includes("copyId"); const handleFetchResponse = (res: Response) => { - res.json().then(resJson => { - setLoading(false) + res.json().then((resJson) => { + setLoading(false); if (res.ok) { - setFormInitData(resJson) + setFormInitData(resJson); } else { - setFetchError(resJson) + setFetchError(resJson); } - }) - } + }); + }; if (isCopy) { getCopy(Number(queryParams.get("copyId"))).then(handleFetchResponse); - } - else if (manifestIdParam) { + } else if (manifestIdParam) { getManifestData(manifestIdParam).then(handleFetchResponse); } else { getManifestDefaults().then(handleFetchResponse); @@ -94,16 +72,9 @@ const Form = (props: any) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const handleSubmit = async (values: any): Promise => { - return fetch(`/api/manifest`, { + return httpClient(`/api/manifest`, { method: "POST", - credentials: "include", - headers: { - "X-XSRF-TOKEN": csrfToken, - Accept: "application/json", - "Content-Type": "application/json", - }, body: JSON.stringify(values), }); }; @@ -115,16 +86,18 @@ const Form = (props: any) => { loading={loading} /> - {loading ? <> : - - withError(fetchError, + {loading ? ( + <> + ) : ( + withError( + fetchError, { - standaloneData.current = data - setErrorData(errors) + standaloneData.current = data; + setErrorData(errors); }} csrfToken={csrfToken} manifestData={formInitData} @@ -132,41 +105,61 @@ const Form = (props: any) => { - + {errorData.map((data, idx) => { - return

{data['dataPath']} {data['message']}

+ return ( +

+ {data["dataPath"]} {data["message"]} +

+ ); })} - + 0} onClick={() => { - const toastId = toast.info("In Progress ...", { autoClose: 50000 }) - handleSubmit(standaloneData.current) - .then((r) => { - r.json().then(resJson => { - if (r.ok) { - toast.update(toastId, { render: 'Updated', type: 'success', autoClose: 3000 }) - if(manifestIdParam) { - setFormInitData(resJson.manifest); - } else { - props.history.push(`/manifests/${resJson.manifest.id}/edit`) - } + const toastId = toast.info("In Progress ...", { + autoClose: 50000, + }); + handleSubmit(standaloneData.current).then((r) => { + r.json().then((resJson) => { + if (r.ok) { + toast.update(toastId, { + render: "Updated", + type: "success", + autoClose: 3000, + }); + if (manifestIdParam) { + setFormInitData(resJson.manifest); } else { - toast.update(toastId, { render: formatDisplayData({ error: resJson.error, message: resJson.message }), type: 'error', autoClose: 5000 }) + props.history.push( + `/manifests/${resJson.manifest.id}/edit` + ); } - }) - }) + } else { + toast.update(toastId, { + render: formatDisplayData({ + error: resJson.error, + message: resJson.message, + }), + type: "error", + autoClose: 5000, + }); + } + }); + }); }} > Submit - +
-
)} + + ) + )} ); }; -export default withCookies(Form); \ No newline at end of file +export default withCookies(Form); diff --git a/src/components/manifest/SchemaForm.tsx b/src/components/manifest/SchemaForm.tsx index 5d02ea2..6951aa8 100644 --- a/src/components/manifest/SchemaForm.tsx +++ b/src/components/manifest/SchemaForm.tsx @@ -6,111 +6,125 @@ */ -import * as React from 'react' -import { JsonForms } from '@jsonforms/react' -import { materialCells, materialRenderers } from '@jsonforms/material-renderers' -import customAjv from '../../configuration/jsonforms/CustomAjv' -import _ from 'lodash' -import { FoldableGroupTester, FoldableGroupRenderer } from '../../renderer/FoldableGroup' -import { FoldableGroupContextProvider } from '../../renderer/FoldableGroupContext' -import { ThemeProvider, CircularProgress } from '@material-ui/core' -import { jsonformThemeOverride } from '../../configuration/jsonforms/Theme' -import './SchemaForm.css' +import * as React from "react"; +import { JsonForms } from "@jsonforms/react"; +import { + materialCells, + materialRenderers, +} from "@jsonforms/material-renderers"; +import customAjv from "../../configuration/jsonforms/CustomAjv"; +import _ from "lodash"; +import { + FoldableGroupTester, + FoldableGroupRenderer, +} from "../../renderer/FoldableGroup"; +import { FoldableGroupContextProvider } from "../../renderer/FoldableGroupContext"; +import { ThemeProvider, CircularProgress } from "@material-ui/core"; +import { jsonformThemeOverride } from "../../configuration/jsonforms/Theme"; +import { httpClient } from "../../helper/api-client"; +import "./SchemaForm.css"; + interface IProps { - csrfToken?: any - name: string - onDataChange?: any - children?: any - updateCompleteSchemaHandler?: any - schemaPath?: string - uiSchema?: any - manifestData?: any + csrfToken?: any; + name: string; + onDataChange?: any; + children?: any; + updateCompleteSchemaHandler?: any; + schemaPath?: string; + uiSchema?: any; + manifestData?: any; } function isProperJsonSchema(completeSchema: any) { - let jsonSchema = completeSchema.jsonSchema - const schemaStr = JSON.stringify(jsonSchema) - let isProperSchema = schemaStr.search("resource:/") === -1 - return isProperSchema + let jsonSchema = completeSchema.jsonSchema; + const schemaStr = JSON.stringify(jsonSchema); + let isProperSchema = schemaStr.search("resource:/") === -1; + return isProperSchema; } export default function SchemaForm(props: IProps) { - const [completeSchema, setCompleteSchema] = React.useState({ uiSchema: undefined, jsonSchema: undefined }) - const [isLoading, setIsLoading] = React.useState(true) + const [completeSchema, setCompleteSchema] = React.useState({ + uiSchema: undefined, + jsonSchema: undefined, + }); + const [isLoading, setIsLoading] = React.useState(true); - React.useEffect(() => { - console.log(`Getting schema for ${props.name}`) - fetch(`/api/schema/${props.name}`, { - method: 'GET', - credentials: 'include', - headers: { - "X-XSRF-TOKEN": props.csrfToken, - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - }).then((res) => { - res.json().then((resBody) => { - if (props.uiSchema) { - props.uiSchema.push(JSON.parse(resBody.data.uiSchema)) - } else { - setCompleteSchema({ uiSchema: JSON.parse(resBody.data.uiSchema), jsonSchema: undefined }) + React.useEffect(() => { + console.log(`Getting schema for ${props.name}`); + httpClient(`/api/schema/${props.name}`).then((res) => { + res.json().then((resBody) => { + if (props.uiSchema) { + props.uiSchema.push(JSON.parse(resBody.data.uiSchema)); + } else { + setCompleteSchema({ + uiSchema: JSON.parse(resBody.data.uiSchema), + jsonSchema: undefined, + }); + } + + const schemaHandler = + props.updateCompleteSchemaHandler || setCompleteSchema; + schemaHandler((prevSchema) => { + _.set( + prevSchema, + `jsonSchema${props.schemaPath ? props.schemaPath : ""}`, + JSON.parse(resBody.data.jsonSchema) + ); + //Creating new object here because if objectId is not changed react doesnt rerenders it + const newSchema = { + uiSchema: prevSchema.uiSchema, + jsonSchema: prevSchema.jsonSchema, + }; + return newSchema; + }); + setIsLoading(false); + }); + }); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return !isLoading ? ( +
+ {React.Children.map(props.children, (child) => { + return React.cloneElement(child, { + updateCompleteSchemaHandler: + props.updateCompleteSchemaHandler || setCompleteSchema, + schemaPath: `${props.schemaPath || ""}.properties.${ + child.props.name + }`, + uiSchema: (props.uiSchema || completeSchema.uiSchema).elements, + }); + })} + + {!props.updateCompleteSchemaHandler && + isProperJsonSchema(completeSchema) ? ( + + + { + if (props.onDataChange) { + props.onDataChange(data, errors); } - - const schemaHandler = props.updateCompleteSchemaHandler || setCompleteSchema - schemaHandler(prevSchema => { - _.set(prevSchema, `jsonSchema${props.schemaPath ? props.schemaPath : ''}`, JSON.parse(resBody.data.jsonSchema)) - //Creating new object here because if objectId is not changed react doesnt rerenders it - const newSchema = { uiSchema: prevSchema.uiSchema, jsonSchema: prevSchema.jsonSchema } - return newSchema - }) - setIsLoading(false) - }) - }) - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - - return ( - ( - !isLoading ? -
- {React.Children.map(props.children, child => { - return React.cloneElement(child, - { - updateCompleteSchemaHandler: props.updateCompleteSchemaHandler || setCompleteSchema, - schemaPath: `${props.schemaPath || ''}.properties.${child.props.name}`, - uiSchema: (props.uiSchema || completeSchema.uiSchema).elements - }) - })} - - - { - (!props.updateCompleteSchemaHandler && isProperJsonSchema(completeSchema)) ? - - - { - if (props.onDataChange) { - props.onDataChange(data, errors) - } - }} /> - - - : null - } - -
: - - ) - ) -} \ No newline at end of file + }} + /> +
+
+ ) : null} +
+ ) : ( + + ); +} diff --git a/src/components/manifest/ShowDeploymentManifest.tsx b/src/components/manifest/ShowDeploymentManifest.tsx index dbcb36b..114f6be 100644 --- a/src/components/manifest/ShowDeploymentManifest.tsx +++ b/src/components/manifest/ShowDeploymentManifest.tsx @@ -1,40 +1,52 @@ import React from "react"; import { Button, makeStyles, TextField } from "@material-ui/core"; -import Autocomplete from '@material-ui/lab/Autocomplete'; +import Autocomplete from "@material-ui/lab/Autocomplete"; import MenuAppBar from "../layout/Header"; import { withCookies } from "react-cookie"; import _ from "lodash"; import { Link } from "react-router-dom"; +import { httpClient } from "../../helper/api-client"; const useStyles = makeStyles((theme) => ({ root: { - '& > *': { + "& > *": { margin: theme.spacing(1), }, }, })); export const ManifestListActions = (props: any) => { - const classes = useStyles() + const classes = useStyles(); return (
- - + +
); }; function fetchListOfManifests(): Promise { - return fetch(`/api/manifest/list`, { - method: "GET", - credentials: "include", - }) - .then((res) => res.json()) + return httpClient(`/api/manifest/list`) + .then((res) => { + return res.json(); + }) .then((response) => { return response; }) @@ -46,34 +58,52 @@ function fetchListOfManifests(): Promise { const ShowDeploymentManifest = () => { const { useState } = React; - const [manifestNameList, setManifestNameList] = useState([]) - const [selectedManifestId, setSelectedManifestId] = useState() + const [manifestNameList, setManifestNameList] = useState([]); + const [selectedManifestId, setSelectedManifestId] = useState(); React.useEffect(() => { - fetchListOfManifests().then(manifestList => { - setManifestNameList(manifestList) - }) - }, []) + fetchListOfManifests().then((manifestList) => { + setManifestNameList(manifestList); + }); + }, []); return ( <> -
+
`${option.environment}/${option.name}`} - renderInput={(params) => } - onChange={(_, value) => value ? setSelectedManifestId(value.id) : setSelectedManifestId(undefined)} + getOptionLabel={(option: any) => + `${option.environment}/${option.name}` + } + renderInput={(params) => ( + + )} + onChange={(_, value) => + value + ? setSelectedManifestId(value.id) + : setSelectedManifestId(undefined) + } />
- +
); diff --git a/src/components/manifest/ShowJson.tsx b/src/components/manifest/ShowJson.tsx index 12369f8..399d3e3 100644 --- a/src/components/manifest/ShowJson.tsx +++ b/src/components/manifest/ShowJson.tsx @@ -1,32 +1,23 @@ import { withCookies } from "react-cookie"; import * as React from "react"; +import { httpClient } from "../../helper/api-client"; const ShowJson = (props: any) => { + const [jsonData, setJsonData] = React.useState({}); - const [jsonData, setJsonData] = React.useState({}); + const getManifestData = async (manifestId: number): Promise => { + console.log(`Fetching manifest id ${manifestId}`); + return httpClient(`/api/manifest/${manifestId}`).then((res) => res.json()); + }; - const getManifestData = async (manifestId: number): Promise => { - console.log(`Fetching manifest id ${manifestId}`); - return fetch(`/api/manifest/${manifestId}`, { - method: "GET", - credentials: "include", - headers: { - "X-XSRF-TOKEN": props.csrfToken, - Accept: "application/json", - "Content-Type": "application/json", - }, - }).then((res) => res.json()); - }; + React.useEffect(() => { + const manifestIdParam = props?.match?.params?.manifestId; + getManifestData(manifestIdParam).then((res) => { + setJsonData(res); + }); + }, []); - React.useEffect(() => { - const manifestIdParam = props?.match?.params?.manifestId; - getManifestData(manifestIdParam).then((res) => { - setJsonData(res); - }); - }, []); + return
{JSON.stringify(jsonData, null, 2)}
; +}; - return (
{JSON.stringify(jsonData, null, 2)}
) - -} - -export default withCookies(ShowJson) \ No newline at end of file +export default withCookies(ShowJson); diff --git a/src/helper/api-client.ts b/src/helper/api-client.ts new file mode 100644 index 0000000..6c4e0bb --- /dev/null +++ b/src/helper/api-client.ts @@ -0,0 +1,36 @@ +import Cookies from "js-cookie"; + +export function httpClient( + endpoint, + { method, headers, ...customConfig }: any = {} +) { + method = method ? method : "GET"; + headers = headers ? headers : {}; + + let csrfHeader = { + "X-XSRF-TOKEN": Cookies.get("XSRF-TOKEN"), + }; + let defaultHeader = { + Accept: "application/json", + "Content-Type": "application/json", + }; + + defaultHeader = + method == "GET" ? defaultHeader : Object.assign(defaultHeader, csrfHeader); + + Object.assign(headers, defaultHeader); + + let config = { + method: method, + headers: headers, + credentials: "include", + ...customConfig, + }; + + return window.fetch(`${endpoint}`, config).then(async (response) => { + if (response.redirected) { + window.location.href = response.url; + } + return response; + }); +} diff --git a/yarn.lock b/yarn.lock index af7c167..d82de55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6988,6 +6988,11 @@ jest@24.9.0, jest@^24.9.0: import-local "^2.0.0" jest-cli "^24.9.0" +js-cookie@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"