added export and delete (#63)

* added export and delete

* added types
This commit is contained in:
Erik Jan de Wit 2020-09-09 16:34:05 +02:00 committed by GitHub
parent 8fbcf7582a
commit 7f66895631
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 26 deletions

View file

@ -20,6 +20,7 @@
"@patternfly/react-core": "^4.40.4", "@patternfly/react-core": "^4.40.4",
"@patternfly/react-icons": "^4.7.2", "@patternfly/react-icons": "^4.7.2",
"@patternfly/react-table": "^4.15.5", "@patternfly/react-table": "^4.15.5",
"file-saver": "^2.0.2",
"i18next": "^19.6.2", "i18next": "^19.6.2",
"i18next-http-backend": "^1.0.18", "i18next-http-backend": "^1.0.18",
"keycloak-js": "^11.0.0", "keycloak-js": "^11.0.0",
@ -47,6 +48,7 @@
"@testing-library/react": "^10.4.6", "@testing-library/react": "^10.4.6",
"@types/dot": "^1.1.4", "@types/dot": "^1.1.4",
"@types/enzyme": "^3.10.5", "@types/enzyme": "^3.10.5",
"@types/file-saver": "^2.0.1",
"@types/jest": "^26.0.4", "@types/jest": "^26.0.4",
"@types/react": "^16.9.23", "@types/react": "^16.9.23",
"@types/react-dom": "^16.9.5", "@types/react-dom": "^16.9.5",

View file

@ -3,10 +3,14 @@ import { render } from "@testing-library/react";
import clientMock from "./mock-clients.json"; import clientMock from "./mock-clients.json";
import { ClientList } from "./ClientList"; import { ClientList } from "./ClientList";
import { I18nextProvider } from "react-i18next";
import { i18n } from "../i18n";
test("renders ClientList", () => { test("renders ClientList", () => {
const { getByText } = render( const { getByText } = render(
<I18nextProvider i18n={i18n}>
<ClientList clients={clientMock} baseUrl="http://blog.nerdin.ch" /> <ClientList clients={clientMock} baseUrl="http://blog.nerdin.ch" />
</I18nextProvider>
); );
const headerElement = getByText(/Client ID/i); const headerElement = getByText(/Client ID/i);
expect(headerElement).toBeInTheDocument(); expect(headerElement).toBeInTheDocument();

View file

@ -1,4 +1,4 @@
import React from "react"; import React, { useContext } from "react";
import { import {
Table, Table,
TableBody, TableBody,
@ -7,10 +7,15 @@ import {
IFormatter, IFormatter,
IFormatterValueType, IFormatterValueType,
} from "@patternfly/react-table"; } from "@patternfly/react-table";
import { Badge } from "@patternfly/react-core"; import { Badge, AlertVariant } from "@patternfly/react-core";
import { saveAs } from "file-saver";
import { ExternalLink } from "../components/external-link/ExternalLink"; import { ExternalLink } from "../components/external-link/ExternalLink";
import { ClientRepresentation } from "../model/client-model"; import { ClientRepresentation } from "../model/client-model";
import { HttpClientContext } from "../http-service/HttpClientContext";
import { useAlerts } from "../components/alert/Alerts";
import { AlertPanel } from "../components/alert/AlertPanel";
import { useTranslation } from "react-i18next";
type ClientListProps = { type ClientListProps = {
clients?: ClientRepresentation[]; clients?: ClientRepresentation[];
@ -25,9 +30,15 @@ const columns: (keyof ClientRepresentation)[] = [
]; ];
export const ClientList = ({ baseUrl, clients }: ClientListProps) => { export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
const httpClient = useContext(HttpClientContext)!;
const { t } = useTranslation();
const [add, alerts, hide] = useAlerts();
const convertClientId = (clientId: string) =>
clientId.substring(0, clientId.indexOf("#"));
const enabled = (): IFormatter => (data?: IFormatterValueType) => { const enabled = (): IFormatter => (data?: IFormatterValueType) => {
const field = data!.toString(); const field = data!.toString();
const value = field.substring(0, field.indexOf("#")); const value = convertClientId(field);
return field.indexOf("true") !== -1 ? ( return field.indexOf("true") !== -1 ? (
<>{value}</> <>{value}</>
) : ( ) : (
@ -61,25 +72,67 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
return r; return r;
}) })
.map((c) => { .map((c) => {
return { cells: columns.map((col) => c[col]) }; return { cells: columns.map((col) => c[col]), client: c };
}); });
return ( return (
<>
<AlertPanel alerts={alerts} onCloseAlert={hide} />
<Table <Table
variant={TableVariant.compact} variant={TableVariant.compact}
cells={[ cells={[
{ title: "Client ID", cellFormatters: [enabled()] }, { title: t("Client ID"), cellFormatters: [enabled()] },
"Type", t("Type"),
{ title: "Description", cellFormatters: [emptyFormatter()] }, { title: t("Description"), cellFormatters: [emptyFormatter()] },
{ {
title: "Home URL", title: t("Home URL"),
cellFormatters: [externalLink(), emptyFormatter()], cellFormatters: [externalLink(), emptyFormatter()],
}, },
]} ]}
rows={data} rows={data}
actions={[
{
title: t("Export"),
onClick: (_, rowId) => {
const clientCopy = JSON.parse(JSON.stringify(data[rowId].client));
clientCopy.clientId = convertClientId(clientCopy.clientId);
delete clientCopy.id;
if (clientCopy.protocolMappers) {
for (let i = 0; i < clientCopy.protocolMappers.length; i++) {
delete clientCopy.protocolMappers[i].id;
}
}
saveAs(
new Blob([JSON.stringify(clientCopy, null, 2)], {
type: "application/json",
}),
clientCopy.clientId + ".json"
);
},
},
{
title: t("Delete"),
onClick: (_, rowId) => {
try {
httpClient.doDelete(
`/admin/realms/master/clients/${data[rowId].client.id}`
);
add(t("The client has been deleted"), AlertVariant.success);
} catch (error) {
add(
`${t("Could not delete client:")} ${error}`,
AlertVariant.danger
);
}
},
},
]}
aria-label="Client list" aria-label="Client list"
> >
<TableHeader /> <TableHeader />
<TableBody /> <TableBody />
</Table> </Table>
</>
); );
}; };

View file

@ -74,11 +74,14 @@ export class HttpClient {
await this.makeConfig(config) await this.makeConfig(config)
); );
const contentType = response.headers.get("content-type");
if (contentType && contentType.indexOf("application/json") !== -1) {
try { try {
response.data = await response.json(); response.data = await response.json();
} catch (e) { } catch (e) {
console.warn(e); console.warn(e);
} }
}
if (!response.ok) { if (!response.ok) {
this.handleError(response); this.handleError(response);

View file

@ -4124,6 +4124,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
"@types/file-saver@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.1.tgz#e18eb8b069e442f7b956d313f4fadd3ef887354e"
integrity sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw==
"@types/glob-base@^0.3.0": "@types/glob-base@^0.3.0":
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/@types/glob-base/-/glob-base-0.3.0.tgz#a581d688347e10e50dd7c17d6f2880a10354319d" resolved "https://registry.yarnpkg.com/@types/glob-base/-/glob-base-0.3.0.tgz#a581d688347e10e50dd7c17d6f2880a10354319d"
@ -9165,6 +9170,11 @@ file-loader@^6.0.0:
loader-utils "^2.0.0" loader-utils "^2.0.0"
schema-utils "^2.7.1" schema-utils "^2.7.1"
file-saver@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.2.tgz#06d6e728a9ea2df2cce2f8d9e84dfcdc338ec17a"
integrity sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==
file-selector@^0.1.8: file-selector@^0.1.8:
version "0.1.12" version "0.1.12"
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.12.tgz#fe726547be219a787a9dcc640575a04a032b1fd0" resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.12.tgz#fe726547be219a787a9dcc640575a04a032b1fd0"