added delete and cypress test
This commit is contained in:
parent
5a8762b3fa
commit
949a0e198b
10 changed files with 176 additions and 58 deletions
|
@ -6,6 +6,7 @@ import CreateClientPage from "../support/pages/admin_console/manage/clients/Crea
|
||||||
import ModalUtils from "../support/util/ModalUtils";
|
import ModalUtils from "../support/util/ModalUtils";
|
||||||
import AdvancedTab from "../support/pages/admin_console/manage/clients/AdvancedTab";
|
import AdvancedTab from "../support/pages/admin_console/manage/clients/AdvancedTab";
|
||||||
import AdminClient from "../support/util/AdminClient";
|
import AdminClient from "../support/util/AdminClient";
|
||||||
|
import InitialAccessTokenTab from "../support/pages/admin_console/manage/clients/InitialAccessTokenTab";
|
||||||
|
|
||||||
let itemId = "client_crud";
|
let itemId = "client_crud";
|
||||||
const loginPage = new LoginPage();
|
const loginPage = new LoginPage();
|
||||||
|
@ -78,6 +79,27 @@ describe("Clients test", function () {
|
||||||
|
|
||||||
listingPage.itemExist(itemId, false);
|
listingPage.itemExist(itemId, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Initial access token", () => {
|
||||||
|
const initialAccessTokenTab = new InitialAccessTokenTab();
|
||||||
|
listingPage.goToInitialAccessTokenTab();
|
||||||
|
initialAccessTokenTab.shouldBeEmpty();
|
||||||
|
initialAccessTokenTab.createNewToken(1, 1).save();
|
||||||
|
|
||||||
|
modalUtils.checkModalTitle("Initial access token details").closeModal();
|
||||||
|
|
||||||
|
initialAccessTokenTab.shouldNotBeEmpty();
|
||||||
|
|
||||||
|
initialAccessTokenTab.getFistId((id) => {
|
||||||
|
listingPage.deleteItem(id);
|
||||||
|
modalUtils
|
||||||
|
.checkModalTitle("Delete initial access token?")
|
||||||
|
.confirmModal();
|
||||||
|
masthead.checkNotificationMessage(
|
||||||
|
"initial access token created successfully"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Advanced tab test", () => {
|
describe("Advanced tab test", () => {
|
||||||
|
|
|
@ -7,6 +7,7 @@ export default class ListingPage {
|
||||||
searchBtn: string;
|
searchBtn: string;
|
||||||
createBtn: string;
|
createBtn: string;
|
||||||
importBtn: string;
|
importBtn: string;
|
||||||
|
initialAccessTokenTab = "initialAccessToken";
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.searchInput = '.pf-c-toolbar__item [type="search"]';
|
this.searchInput = '.pf-c-toolbar__item [type="search"]';
|
||||||
|
@ -34,15 +35,20 @@ export default class ListingPage {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
goToInitialAccessTokenTab() {
|
||||||
|
cy.getId(this.initialAccessTokenTab).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
searchItem(searchValue: string, wait = true) {
|
searchItem(searchValue: string, wait = true) {
|
||||||
if (wait) {
|
if (wait) {
|
||||||
const searchUrl = `/admin/realms/master/*${searchValue}*`;
|
const searchUrl = `/admin/realms/master/*${searchValue}*`;
|
||||||
cy.intercept(searchUrl).as("searchClients");
|
cy.intercept(searchUrl).as("search");
|
||||||
}
|
}
|
||||||
cy.get(this.searchInput).type(searchValue);
|
cy.get(this.searchInput).type(searchValue);
|
||||||
cy.get(this.searchBtn).click();
|
cy.get(this.searchBtn).click();
|
||||||
if (wait) {
|
if (wait) {
|
||||||
cy.wait(["@searchClients"]);
|
cy.wait(["@search"]);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
export default class InitialAccessTokenTab {
|
||||||
|
private emptyAction = "empty-primary-action";
|
||||||
|
|
||||||
|
private expirationInput = "expiration";
|
||||||
|
private countInput = "count";
|
||||||
|
private saveBtn = "save";
|
||||||
|
|
||||||
|
shouldBeEmpty() {
|
||||||
|
cy.getId(this.emptyAction).should("exist");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldNotBeEmpty() {
|
||||||
|
cy.getId(this.emptyAction).should("not.exist");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFistId(callback: (id: string) => void) {
|
||||||
|
cy.get('tbody > tr > [data-label="ID"]')
|
||||||
|
.invoke("text")
|
||||||
|
.then((text) => {
|
||||||
|
callback(text);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
createNewToken(expiration: number, count: number) {
|
||||||
|
cy.getId(this.emptyAction).click();
|
||||||
|
cy.getId(this.expirationInput).type(`${expiration}`);
|
||||||
|
cy.getId(this.countInput).type(`${count}`);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
cy.getId(this.saveBtn).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,7 @@
|
||||||
"@patternfly/react-table": "4.23.0",
|
"@patternfly/react-table": "4.23.0",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"i18next": "^19.6.2",
|
"i18next": "^19.6.2",
|
||||||
"keycloak-admin": "1.14.9",
|
"keycloak-admin": "1.14.10",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"react": "^16.8.5",
|
"react": "^16.8.5",
|
||||||
|
|
|
@ -81,7 +81,11 @@ export const CreateInitialAccessToken = () => {
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
control={control}
|
control={control}
|
||||||
render={({ onChange, value }) => (
|
render={({ onChange, value }) => (
|
||||||
<TimeSelector value={value} onChange={onChange} />
|
<TimeSelector
|
||||||
|
data-testid="expiration"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@ -102,6 +106,7 @@ export const CreateInitialAccessToken = () => {
|
||||||
control={control}
|
control={control}
|
||||||
render={({ onChange, value }) => (
|
render={({ onChange, value }) => (
|
||||||
<NumberInput
|
<NumberInput
|
||||||
|
data-testid="count"
|
||||||
inputName="count"
|
inputName="count"
|
||||||
inputAriaLabel={t("count")}
|
inputAriaLabel={t("count")}
|
||||||
min={1}
|
min={1}
|
||||||
|
@ -116,10 +121,11 @@ export const CreateInitialAccessToken = () => {
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<Button variant="primary" type="submit">
|
<Button variant="primary" type="submit" data-testid="save">
|
||||||
{t("common:save")}
|
{t("common:save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
data-testid="cancel"
|
||||||
variant="link"
|
variant="link"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
history.push(`/${realm}/clients/initialAccessToken`)
|
history.push(`/${realm}/clients/initialAccessToken`)
|
||||||
|
|
|
@ -1,70 +1,109 @@
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { useHistory, useRouteMatch } from "react-router-dom";
|
import { useHistory, useRouteMatch } from "react-router-dom";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button } from "@patternfly/react-core";
|
import { AlertVariant, Button, ButtonVariant } from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import ClientInitialAccessPresentation from "keycloak-admin/lib/defs/clientInitialAccessPresentation";
|
||||||
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
||||||
|
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||||
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
|
|
||||||
export const InitialAccessTokenList = () => {
|
export const InitialAccessTokenList = () => {
|
||||||
const { t } = useTranslation("clients");
|
const { t } = useTranslation("clients");
|
||||||
|
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
|
const { addAlert } = useAlerts();
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { url } = useRouteMatch();
|
const { url } = useRouteMatch();
|
||||||
|
|
||||||
|
const [token, setToken] = useState<ClientInitialAccessPresentation>();
|
||||||
|
|
||||||
const loader = async () =>
|
const loader = async () =>
|
||||||
await adminClient.realms.getClientsInitialAccess({ realm });
|
await adminClient.realms.getClientsInitialAccess({ realm });
|
||||||
|
|
||||||
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
|
titleKey: "clients:tokenDeleteConfirmTitle",
|
||||||
|
messageKey: t("tokenDeleteConfirm", token),
|
||||||
|
continueButtonLabel: "common:delete",
|
||||||
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
await adminClient.realms.delClientsInitialAccess({
|
||||||
|
realm,
|
||||||
|
id: token!.id!,
|
||||||
|
});
|
||||||
|
addAlert(t("tokenDeleteSuccess"), AlertVariant.success);
|
||||||
|
setToken(undefined);
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(t("tokenDeleteError", { error }), AlertVariant.danger);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeycloakDataTable
|
<>
|
||||||
ariaLabelKey="clients:initialAccessToken"
|
<DeleteConfirm />
|
||||||
searchPlaceholderKey="clients:searchInitialAccessToken"
|
<KeycloakDataTable
|
||||||
loader={loader}
|
key={token?.id}
|
||||||
toolbarItem={
|
ariaLabelKey="clients:initialAccessToken"
|
||||||
<>
|
searchPlaceholderKey="clients:searchInitialAccessToken"
|
||||||
<Button onClick={() => history.push(`${url}/create`)}>
|
loader={loader}
|
||||||
{t("common:create")}
|
toolbarItem={
|
||||||
</Button>
|
<>
|
||||||
</>
|
<Button onClick={() => history.push(`${url}/create`)}>
|
||||||
}
|
{t("common:create")}
|
||||||
columns={[
|
</Button>
|
||||||
{
|
</>
|
||||||
name: "id",
|
}
|
||||||
displayKey: "clients:id",
|
actions={[
|
||||||
},
|
{
|
||||||
{
|
title: t("common:delete"),
|
||||||
name: "timestamp",
|
onRowClick: (token) => {
|
||||||
displayKey: "clients:timestamp",
|
setToken(token);
|
||||||
cellRenderer: (row) => moment(row.timestamp! * 1000).format("LLL"),
|
toggleDeleteDialog();
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
name: "expiration",
|
]}
|
||||||
displayKey: "clients:expires",
|
columns={[
|
||||||
cellRenderer: (row) =>
|
{
|
||||||
moment(row.timestamp! * 1000 + row.expiration! * 1000).fromNow(),
|
name: "id",
|
||||||
},
|
displayKey: "clients:id",
|
||||||
{
|
},
|
||||||
name: "count",
|
{
|
||||||
displayKey: "clients:count",
|
name: "timestamp",
|
||||||
},
|
displayKey: "clients:timestamp",
|
||||||
{
|
cellRenderer: (row) => moment(row.timestamp! * 1000).format("LLL"),
|
||||||
name: "remainingCount",
|
},
|
||||||
displayKey: "clients:remainingCount",
|
{
|
||||||
},
|
name: "expiration",
|
||||||
]}
|
displayKey: "clients:expires",
|
||||||
emptyState={
|
cellRenderer: (row) =>
|
||||||
<ListEmptyState
|
moment(row.timestamp! * 1000 + row.expiration! * 1000).fromNow(),
|
||||||
message={t("noTokens")}
|
},
|
||||||
instructions={t("noTokensInstructions")}
|
{
|
||||||
primaryActionText={t("common:create")}
|
name: "count",
|
||||||
onPrimaryAction={() => history.push(`${url}/create`)}
|
displayKey: "clients:count",
|
||||||
/>
|
},
|
||||||
}
|
{
|
||||||
/>
|
name: "remainingCount",
|
||||||
|
displayKey: "clients:remainingCount",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
emptyState={
|
||||||
|
<ListEmptyState
|
||||||
|
message={t("noTokens")}
|
||||||
|
instructions={t("noTokensInstructions")}
|
||||||
|
primaryActionText={t("common:create")}
|
||||||
|
onPrimaryAction={() => history.push(`${url}/create`)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,6 +75,10 @@
|
||||||
"clientDeleteConfirm": "If you delete this client, all associated data will be removed.",
|
"clientDeleteConfirm": "If you delete this client, all associated data will be removed.",
|
||||||
"searchInitialAccessToken": "Search token",
|
"searchInitialAccessToken": "Search token",
|
||||||
"createToken": "Create initial access token",
|
"createToken": "Create initial access token",
|
||||||
|
"tokenDeleteConfirm": "Are you sure you want to permanently delete the initial access token {{id}}",
|
||||||
|
"tokenDeleteConfirmTitle": "Delete initial access token?",
|
||||||
|
"tokenDeleteSuccess": "initial access token created successfully",
|
||||||
|
"tokenDeleteError": "Could not delete initial access token: '{{error}}'",
|
||||||
"id": "ID",
|
"id": "ID",
|
||||||
"timestamp": "Created date",
|
"timestamp": "Created date",
|
||||||
"expires": "Expires",
|
"expires": "Expires",
|
||||||
|
|
|
@ -5,13 +5,14 @@ import {
|
||||||
Split,
|
Split,
|
||||||
SplitItem,
|
SplitItem,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
TextInputProps,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export type Unit = "seconds" | "minutes" | "hours" | "days";
|
export type Unit = "seconds" | "minutes" | "hours" | "days";
|
||||||
|
|
||||||
export type TimeSelectorProps = {
|
export type TimeSelectorProps = TextInputProps & {
|
||||||
value: number;
|
value: number;
|
||||||
units?: Unit[];
|
units?: Unit[];
|
||||||
onChange: (time: number | string) => void;
|
onChange: (time: number | string) => void;
|
||||||
|
@ -21,6 +22,7 @@ export const TimeSelector = ({
|
||||||
value,
|
value,
|
||||||
units = ["seconds", "minutes", "hours", "days"],
|
units = ["seconds", "minutes", "hours", "days"],
|
||||||
onChange,
|
onChange,
|
||||||
|
...rest
|
||||||
}: TimeSelectorProps) => {
|
}: TimeSelectorProps) => {
|
||||||
const { t } = useTranslation("common");
|
const { t } = useTranslation("common");
|
||||||
|
|
||||||
|
@ -73,6 +75,7 @@ export const TimeSelector = ({
|
||||||
<Split hasGutter>
|
<Split hasGutter>
|
||||||
<SplitItem>
|
<SplitItem>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
{...rest}
|
||||||
type="number"
|
type="number"
|
||||||
id={`kc-time-${new Date().getTime()}`}
|
id={`kc-time-${new Date().getTime()}`}
|
||||||
min="0"
|
min="0"
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const UsersInRoleTab = () => {
|
||||||
name: role.name!,
|
name: role.name!,
|
||||||
first: first!,
|
first: first!,
|
||||||
max: max!,
|
max: max!,
|
||||||
} as any);
|
});
|
||||||
return usersWithRole;
|
return usersWithRole;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13477,10 +13477,10 @@ junk@^3.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
|
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
|
||||||
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
|
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
|
||||||
|
|
||||||
keycloak-admin@1.14.9:
|
keycloak-admin@1.14.10:
|
||||||
version "1.14.9"
|
version "1.14.10"
|
||||||
resolved "https://registry.yarnpkg.com/keycloak-admin/-/keycloak-admin-1.14.9.tgz#f068e8580714c3b92987901cfe6c83bfba669a2a"
|
resolved "https://registry.yarnpkg.com/keycloak-admin/-/keycloak-admin-1.14.10.tgz#e44903826896262b3655303db46795b84a5f9b08"
|
||||||
integrity sha512-VaS6unTYLWuEkqb+FAY+c0Oo+ta505OxhOntCHLcgysMwRmL5pz6taVK8lUZ1ir/6QVto62adVQjmN5fMkpSWg==
|
integrity sha512-WhEA+FkcPikN/Oqh7L0puVkPU1cm3bB+15VOoPdESZknQ9poS0Ohz3Rg1flRfmMdqoMgcy+prigUPtHy6gOAUg==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^0.21.0"
|
axios "^0.21.0"
|
||||||
camelize "^1.0.0"
|
camelize "^1.0.0"
|
||||||
|
|
Loading…
Reference in a new issue