INFRA-447| Deepak Jain| creating api client wrapper

This commit is contained in:
Deepak Jain
2020-07-30 15:29:40 +05:30
parent 3b10628eda
commit 4aca1458be
8 changed files with 309 additions and 233 deletions

View File

@@ -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",

View File

@@ -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) {
)}
</Toolbar>
</AppBar>
{props.loading ? <div className={classes.root}><LinearProgress color='secondary' /></div> : <></>}
{props.loading ? (
<div className={classes.root}>
<LinearProgress color="secondary" />
</div>
) : (
<></>
)}
</div>
);
}

View File

@@ -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 <Alert severity="error">{formatDisplayData(error)}</Alert>
return <Alert severity="error">{formatDisplayData(error)}</Alert>;
}
}
};
const Form = (props: any) => {
const { cookies } = props;
const standaloneData = useRef<Object>({});
const [formInitData, setFormInitData] = useState<Object>({});
const [loading, setLoading] = useState<boolean>(true)
const [fetchError, setFetchError] = useState<Object>({})
const [loading, setLoading] = useState<boolean>(true);
const [fetchError, setFetchError] = useState<Object>({});
const manifestIdParam = props?.match?.params?.manifestId;
const [csrfToken] = useState(cookies.get("XSRF-TOKEN"));
const [errorData, setErrorData] = useState<Object[]>([]);
const getManifestData = async (manifestId: number): Promise<Response> => {
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<Response> => {
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<Response> => {
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<any> => {
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,
<Grid container style={{ padding: "2em 6em 2em 6em" }}>
<Grid item xs={12}>
<SchemaForm
name="manifest"
onDataChange={(data, errors) => {
standaloneData.current = data
setErrorData(errors)
standaloneData.current = data;
setErrorData(errors);
}}
csrfToken={csrfToken}
manifestData={formInitData}
@@ -132,41 +105,61 @@ const Form = (props: any) => {
<SchemaForm name="deployment" />
<SchemaForm name="extraResources" />
</SchemaForm>
{errorData.map((data, idx) => {
return <p style={{ color: "red" }} key={idx}>{data['dataPath']} {data['message']}</p>
return (
<p style={{ color: "red" }} key={idx}>
{data["dataPath"]} {data["message"]}
</p>
);
})}
<ConfirmationButton
type="submit"
variant="contained"
style={{ float: "right" }}
disabled={errorData.length > 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
</ConfirmationButton>
</ConfirmationButton>
</Grid>
</Grid>)}
</Grid>
)
)}
</div>
);
};
export default withCookies(Form);
export default withCookies(Form);

View File

@@ -6,111 +6,125 @@
</SchemaForm>
*/
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 ? (
<div>
{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) ? (
<FoldableGroupContextProvider>
<ThemeProvider theme={jsonformThemeOverride}>
<JsonForms
schema={completeSchema.jsonSchema}
ajv={customAjv}
uischema={completeSchema.uiSchema}
data={props.manifestData}
renderers={[
...materialRenderers,
{
tester: FoldableGroupTester,
renderer: FoldableGroupRenderer,
},
]}
cells={materialCells}
onChange={({ data, errors }) => {
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 ?
<div>
{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)) ?
<FoldableGroupContextProvider>
<ThemeProvider theme={jsonformThemeOverride}>
<JsonForms
schema={completeSchema.jsonSchema}
ajv={customAjv}
uischema={completeSchema.uiSchema}
data={props.manifestData}
renderers={[
...materialRenderers,
{ tester: FoldableGroupTester, renderer: FoldableGroupRenderer }
]}
cells={materialCells}
onChange={({ data, errors }) => {
if (props.onDataChange) {
props.onDataChange(data, errors)
}
}} />
</ThemeProvider>
</FoldableGroupContextProvider>
: null
}
</div> :
<CircularProgress size={60} />
)
)
}
}}
/>
</ThemeProvider>
</FoldableGroupContextProvider>
) : null}
</div>
) : (
<CircularProgress size={60} />
);
}

View File

@@ -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 (
<div className={classes.root}>
<Button color="secondary" component={Link} to={`/manifests/${props.manifestId}/edit`} variant="outlined" disabled={!_.isNumber(props.manifestId)}>
<Button
color="secondary"
component={Link}
to={`/manifests/${props.manifestId}/edit`}
variant="outlined"
disabled={!_.isNumber(props.manifestId)}
>
Show
</Button>
<Button color="primary" component={Link} to={`/manifests/create?copyId=${props.manifestId}`} variant="outlined" disabled={!_.isNumber(props.manifestId)}>
</Button>
<Button
color="primary"
component={Link}
to={`/manifests/create?copyId=${props.manifestId}`}
variant="outlined"
disabled={!_.isNumber(props.manifestId)}
>
Clone
</Button>
</Button>
</div>
);
};
function fetchListOfManifests(): Promise<any[]> {
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<any[]> {
const ShowDeploymentManifest = () => {
const { useState } = React;
const [manifestNameList, setManifestNameList] = useState<any>([])
const [selectedManifestId, setSelectedManifestId] = useState()
const [manifestNameList, setManifestNameList] = useState<any>([]);
const [selectedManifestId, setSelectedManifestId] = useState();
React.useEffect(() => {
fetchListOfManifests().then(manifestList => {
setManifestNameList(manifestList)
})
}, [])
fetchListOfManifests().then((manifestList) => {
setManifestNameList(manifestList);
});
}, []);
return (
<>
<MenuAppBar name="List Manifests" />
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column", height: "80vh" }} >
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
height: "80vh",
}}
>
<div style={{ width: "20vw" }}>
<Autocomplete
id="combo-box-demo"
options={manifestNameList}
getOptionLabel={(option: any) => `${option.environment}/${option.name}`}
renderInput={(params) => <TextField {...params} label="Select Manifest" variant="standard" fullWidth />}
onChange={(_, value) => value ? setSelectedManifestId(value.id) : setSelectedManifestId(undefined)}
getOptionLabel={(option: any) =>
`${option.environment}/${option.name}`
}
renderInput={(params) => (
<TextField
{...params}
label="Select Manifest"
variant="standard"
fullWidth
/>
)}
onChange={(_, value) =>
value
? setSelectedManifestId(value.id)
: setSelectedManifestId(undefined)
}
/>
</div>
<div style={{ height: "2vh" }} />
<ManifestListActions
manifestId={selectedManifestId}
show={true}
/>
<ManifestListActions manifestId={selectedManifestId} show={true} />
</div>
</>
);

View File

@@ -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<any> => {
console.log(`Fetching manifest id ${manifestId}`);
return httpClient(`/api/manifest/${manifestId}`).then((res) => res.json());
};
const getManifestData = async (manifestId: number): Promise<any> => {
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 <pre>{JSON.stringify(jsonData, null, 2)}</pre>;
};
return (<pre>{JSON.stringify(jsonData, null, 2)}</pre>)
}
export default withCookies(ShowJson)
export default withCookies(ShowJson);

36
src/helper/api-client.ts Normal file
View File

@@ -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;
});
}

View File

@@ -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"