enterprise: fix license status progress bar (#11048)
* clamp width to 100% width Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add case for unlicensed and set to infinity when users of a type exists that dont have licenses Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rework license status into separate component... Signed-off-by: Jens Langhammer <jens@goauthentik.io> * enable coverage Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove annoying disable-search-engine-choice-screen Signed-off-by: Jens Langhammer <jens@goauthentik.io> * refactor percentage calculation Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix a bug found by tests, yay Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add tests for enterprise status card Signed-off-by: Jens Langhammer <jens@goauthentik.io> * upgrade vite-tsconfig-paths Signed-off-by: Jens Langhammer <jens@goauthentik.io> * ...? Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
7
Makefile
7
Makefile
@ -43,7 +43,7 @@ help: ## Show this help
|
||||
sort
|
||||
@echo ""
|
||||
|
||||
test-go:
|
||||
go-test:
|
||||
go test -timeout 0 -v -race -cover ./...
|
||||
|
||||
test-docker: ## Run all tests in a docker-compose
|
||||
@ -205,11 +205,14 @@ gen: gen-build gen-client-ts
|
||||
web-build: web-install ## Build the Authentik UI
|
||||
cd web && npm run build
|
||||
|
||||
web: web-lint-fix web-lint web-check-compile ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it
|
||||
web: web-lint-fix web-lint web-check-compile web-test ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it
|
||||
|
||||
web-install: ## Install the necessary libraries to build the Authentik UI
|
||||
cd web && npm ci
|
||||
|
||||
web-test: ## Run tests for the Authentik UI
|
||||
cd web && npm run test
|
||||
|
||||
web-watch: ## Build and watch the Authentik UI for changes, updating automatically
|
||||
rm -rf web/dist/
|
||||
mkdir web/dist/
|
||||
|
||||
6
web/package-lock.json
generated
6
web/package-lock.json
generated
@ -120,7 +120,7 @@
|
||||
"turnstile-types": "^1.2.2",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^8.2.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wdio-wait-for": "^3.0.11",
|
||||
"wireit": "^0.14.8"
|
||||
},
|
||||
@ -24860,7 +24860,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite-tsconfig-paths": {
|
||||
"version": "4.3.2",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.0.1.tgz",
|
||||
"integrity": "sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@ -108,7 +108,7 @@
|
||||
"turnstile-types": "^1.2.2",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^8.2.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wdio-wait-for": "^3.0.11",
|
||||
"wireit": "^0.14.8"
|
||||
},
|
||||
@ -154,6 +154,7 @@
|
||||
"tsc": "wireit",
|
||||
"watch": "run-s build-locales esbuild:watch"
|
||||
},
|
||||
"type": "module",
|
||||
"wireit": {
|
||||
"build": {
|
||||
"#comment": [
|
||||
@ -323,13 +324,17 @@
|
||||
"command": "node scripts/build-storybook-import-maps.mjs"
|
||||
},
|
||||
"test": {
|
||||
"command": "wdio run ./wdio.conf.ts --logLevel=warn --autoCompileOpts.tsNodeOpts.project=tsconfig.test.json",
|
||||
"command": "wdio run ./wdio.conf.ts --logLevel=warn",
|
||||
"env": {
|
||||
"CI": "true"
|
||||
"CI": "true",
|
||||
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"test-view": {
|
||||
"command": "wdio run ./wdio.conf.ts --autoCompileOpts.tsNodeOpts.project=tsconfig.test.json"
|
||||
"command": "wdio run ./wdio.conf.ts",
|
||||
"env": {
|
||||
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"tsc": {
|
||||
"dependencies": [
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import "@goauthentik/admin/enterprise/EnterpriseLicenseForm";
|
||||
import "@goauthentik/admin/enterprise/EnterpriseStatusCard";
|
||||
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
@ -14,17 +15,14 @@ import { TablePage } from "@goauthentik/elements/table/TablePage";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFProgress from "@patternfly/patternfly/components/Progress/progress.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
import PFSplit from "@patternfly/patternfly/layouts/Split/split.css";
|
||||
|
||||
import {
|
||||
EnterpriseApi,
|
||||
@ -67,13 +65,10 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(
|
||||
PFDescriptionList,
|
||||
PFGrid,
|
||||
PFBanner,
|
||||
PFFormControl,
|
||||
PFButton,
|
||||
PFProgress,
|
||||
PFSplit,
|
||||
PFCard,
|
||||
css`
|
||||
.pf-m-no-padding-bottom {
|
||||
@ -198,103 +193,14 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
</div>
|
||||
</section>
|
||||
<section class="pf-c-page__main-section pf-m-no-padding-bottom">
|
||||
${this.renderCurrentSummary()}
|
||||
<ak-enterprise-status-card
|
||||
.summary=${this.summary}
|
||||
.forecast=${this.forecast}
|
||||
></ak-enterprise-status-card>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
renderSummaryBadge() {
|
||||
switch (this.summary?.status) {
|
||||
case LicenseSummaryStatusEnum.Expired:
|
||||
return html`<ak-label color=${PFColor.Red}>${msg("Expired")}</ak-label>`;
|
||||
case LicenseSummaryStatusEnum.ExpirySoon:
|
||||
return html`<ak-label color=${PFColor.Orange}>${msg("Expiring soon")}</ak-label>`;
|
||||
case LicenseSummaryStatusEnum.Unlicensed:
|
||||
return html`<ak-label color=${PFColor.Grey}>${msg("Unlicensed")}</ak-label>`;
|
||||
case LicenseSummaryStatusEnum.ReadOnly:
|
||||
return html`<ak-label color=${PFColor.Red}>${msg("Read Only")}</ak-label>`;
|
||||
case LicenseSummaryStatusEnum.Valid:
|
||||
return html`<ak-label color=${PFColor.Green}>${msg("Valid")}</ak-label>`;
|
||||
default:
|
||||
return nothing;
|
||||
}
|
||||
}
|
||||
|
||||
renderCurrentSummary() {
|
||||
if (!this.forecast || !this.summary) {
|
||||
return html`${msg("Loading")}`;
|
||||
}
|
||||
const internalUserPercentage =
|
||||
this.summary.internalUsers > 0
|
||||
? Math.ceil(this.forecast.internalUsers / (this.summary.internalUsers / 100))
|
||||
: 0;
|
||||
const externalUserPercentage =
|
||||
this.summary.externalUsers > 0
|
||||
? Math.ceil(this.forecast.externalUsers / (this.summary.externalUsers / 100))
|
||||
: 0;
|
||||
return html`<div class="pf-c-card">
|
||||
<div class="pf-c-card__title">${msg("Current license status")}</div>
|
||||
<div class="pf-c-card__body pf-l-split pf-m-gutter">
|
||||
<dl class="pf-l-split__item pf-c-description-list pf-m-horizontal">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${msg("Overall license status")}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.renderSummaryBadge()}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="pf-l-split__item pf-m-fill">
|
||||
<div class="pf-c-progress">
|
||||
<div class="pf-c-progress__description">${msg("Internal user usage")}</div>
|
||||
<div class="pf-c-progress__status" aria-hidden="true">
|
||||
<span class="pf-c-progress__measure"
|
||||
>${msg(str`${internalUserPercentage}%`)}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-progress__bar"
|
||||
role="progressbar"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="${internalUserPercentage}"
|
||||
>
|
||||
<div
|
||||
class="pf-c-progress__indicator"
|
||||
style="width:${internalUserPercentage}%;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-progress">
|
||||
<div class="pf-c-progress__description">${msg("External user usage")}</div>
|
||||
<div class="pf-c-progress__status" aria-hidden="true">
|
||||
<span class="pf-c-progress__measure"
|
||||
>${msg(str`${externalUserPercentage}%`)}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-progress__bar"
|
||||
role="progressbar"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="${externalUserPercentage}"
|
||||
>
|
||||
<div
|
||||
class="pf-c-progress__indicator"
|
||||
style="width:${externalUserPercentage}%;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
row(item: License): TemplateResult[] {
|
||||
let color = PFColor.Green;
|
||||
if (item.expiry) {
|
||||
|
||||
165
web/src/admin/enterprise/EnterpriseStatusCard.test.ts
Normal file
165
web/src/admin/enterprise/EnterpriseStatusCard.test.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet.js";
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html, render as litRender } from "lit";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { LicenseForecast, LicenseSummary, LicenseSummaryStatusEnum } from "@goauthentik/api";
|
||||
|
||||
import "./EnterpriseStatusCard.js";
|
||||
|
||||
const render = (body: TemplateResult) => {
|
||||
document.adoptedStyleSheets = [
|
||||
...document.adoptedStyleSheets,
|
||||
ensureCSSStyleSheet(PFBase),
|
||||
ensureCSSStyleSheet(AKGlobal),
|
||||
];
|
||||
return litRender(body, document.body);
|
||||
};
|
||||
|
||||
describe("ak-enterprise-status-card", () => {
|
||||
it("should not error when no data is loaded", async () => {
|
||||
render(html`<ak-enterprise-status-card></ak-enterprise-status-card>`);
|
||||
|
||||
const status = await $("ak-enterprise-status-card");
|
||||
await expect(status).toHaveText(msg("Loading"));
|
||||
});
|
||||
|
||||
it("should render empty when unlicensed", async () => {
|
||||
const forecast: LicenseForecast = {
|
||||
externalUsers: 123,
|
||||
internalUsers: 123,
|
||||
forecastedExternalUsers: 123,
|
||||
forecastedInternalUsers: 123,
|
||||
};
|
||||
const summary: LicenseSummary = {
|
||||
status: LicenseSummaryStatusEnum.Unlicensed,
|
||||
internalUsers: 0,
|
||||
externalUsers: 0,
|
||||
latestValid: new Date(0),
|
||||
licenseFlags: [],
|
||||
};
|
||||
render(
|
||||
html`<ak-enterprise-status-card .forecast=${forecast} .summary=${summary}>
|
||||
</ak-enterprise-status-card>`,
|
||||
);
|
||||
|
||||
const status = await $("ak-enterprise-status-card").$(
|
||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||
);
|
||||
await expect(status).toExist();
|
||||
await expect(status).toHaveText(msg("Unlicensed"));
|
||||
|
||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#internalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(internalUserProgress).toExist();
|
||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "0");
|
||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#externalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(externalUserProgress).toExist();
|
||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "0");
|
||||
});
|
||||
|
||||
it("should show warnings when full", async () => {
|
||||
const forecast: LicenseForecast = {
|
||||
externalUsers: 123,
|
||||
internalUsers: 123,
|
||||
forecastedExternalUsers: 123,
|
||||
forecastedInternalUsers: 123,
|
||||
};
|
||||
const summary: LicenseSummary = {
|
||||
status: LicenseSummaryStatusEnum.Valid,
|
||||
internalUsers: 123,
|
||||
externalUsers: 123,
|
||||
latestValid: new Date(),
|
||||
licenseFlags: [],
|
||||
};
|
||||
render(
|
||||
html`<ak-enterprise-status-card .forecast=${forecast} .summary=${summary}>
|
||||
</ak-enterprise-status-card>`,
|
||||
);
|
||||
|
||||
const status = await $("ak-enterprise-status-card").$(
|
||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||
);
|
||||
await expect(status).toExist();
|
||||
await expect(status).toHaveText(msg("Valid"));
|
||||
|
||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#internalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(internalUserProgress).toExist();
|
||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "100");
|
||||
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
|
||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#externalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(externalUserProgress).toExist();
|
||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "100");
|
||||
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#externalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
});
|
||||
|
||||
it("should show infinity when not licensed for a user type", async () => {
|
||||
const forecast: LicenseForecast = {
|
||||
externalUsers: 123,
|
||||
internalUsers: 123,
|
||||
forecastedExternalUsers: 123,
|
||||
forecastedInternalUsers: 123,
|
||||
};
|
||||
const summary: LicenseSummary = {
|
||||
status: LicenseSummaryStatusEnum.Valid,
|
||||
internalUsers: 123,
|
||||
externalUsers: 0,
|
||||
latestValid: new Date(),
|
||||
licenseFlags: [],
|
||||
};
|
||||
render(
|
||||
html`<ak-enterprise-status-card .forecast=${forecast} .summary=${summary}>
|
||||
</ak-enterprise-status-card>`,
|
||||
);
|
||||
|
||||
const status = await $("ak-enterprise-status-card").$(
|
||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||
);
|
||||
await expect(status).toExist();
|
||||
await expect(status).toHaveText(msg("Valid"));
|
||||
|
||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#internalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(internalUserProgress).toExist();
|
||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "100");
|
||||
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
|
||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#externalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(externalUserProgress).toExist();
|
||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "∞");
|
||||
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#externalUsers"),
|
||||
).toHaveElementClass("pf-m-danger");
|
||||
});
|
||||
});
|
||||
157
web/src/admin/enterprise/EnterpriseStatusCard.ts
Normal file
157
web/src/admin/enterprise/EnterpriseStatusCard.ts
Normal file
@ -0,0 +1,157 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { PFColor } from "@goauthentik/elements/Label";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, html, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
import PFProgress from "@patternfly/patternfly/components/Progress/progress.css";
|
||||
import PFSplit from "@patternfly/patternfly/layouts/Split/split.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { LicenseForecast, LicenseSummary, LicenseSummaryStatusEnum } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-enterprise-status-card")
|
||||
export class EnterpriseStatusCard extends AKElement {
|
||||
@state()
|
||||
forecast?: LicenseForecast;
|
||||
|
||||
@state()
|
||||
summary?: LicenseSummary;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFDescriptionList, PFCard, PFSplit, PFProgress];
|
||||
}
|
||||
|
||||
renderSummaryBadge() {
|
||||
switch (this.summary?.status) {
|
||||
case LicenseSummaryStatusEnum.Expired:
|
||||
return html`<ak-label color=${PFColor.Red}>${msg("Expired")}</ak-label>`;
|
||||
case LicenseSummaryStatusEnum.ExpirySoon:
|
||||
return html`<ak-label color=${PFColor.Orange}>${msg("Expiring soon")}</ak-label>`;
|
||||
case LicenseSummaryStatusEnum.Unlicensed:
|
||||
return html`<ak-label color=${PFColor.Grey}>${msg("Unlicensed")}</ak-label>`;
|
||||
case LicenseSummaryStatusEnum.ReadOnly:
|
||||
return html`<ak-label color=${PFColor.Red}>${msg("Read Only")}</ak-label>`;
|
||||
case LicenseSummaryStatusEnum.Valid:
|
||||
return html`<ak-label color=${PFColor.Green}>${msg("Valid")}</ak-label>`;
|
||||
default:
|
||||
return nothing;
|
||||
}
|
||||
}
|
||||
|
||||
calcUserPercentage(licensed: number, current: number) {
|
||||
const percentage = licensed > 0 ? Math.ceil(current / (licensed / 100)) : 0;
|
||||
if (current > 0 && licensed === 0) return Infinity;
|
||||
return percentage;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.forecast || !this.summary) {
|
||||
return html`${msg("Loading")}`;
|
||||
}
|
||||
let internalUserPercentage = 0;
|
||||
let externalUserPercentage = 0;
|
||||
if (this.summary.status !== LicenseSummaryStatusEnum.Unlicensed) {
|
||||
internalUserPercentage = this.calcUserPercentage(
|
||||
this.summary.internalUsers,
|
||||
this.forecast.internalUsers,
|
||||
);
|
||||
externalUserPercentage = this.calcUserPercentage(
|
||||
this.summary.externalUsers,
|
||||
this.forecast.externalUsers,
|
||||
);
|
||||
}
|
||||
return html`<div class="pf-c-card">
|
||||
<div class="pf-c-card__title">${msg("Current license status")}</div>
|
||||
<div class="pf-c-card__body">
|
||||
<div class="pf-l-split pf-m-gutter">
|
||||
<dl class="pf-l-split__item pf-c-description-list pf-m-horizontal">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${msg("Overall license status")}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${this.renderSummaryBadge()}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="pf-l-split__item pf-m-fill">
|
||||
<div
|
||||
class="pf-c-progress ${internalUserPercentage > 100
|
||||
? "pf-m-danger"
|
||||
: ""} ${internalUserPercentage >= 80 ? "pf-m-warning" : ""}"
|
||||
id="internalUsers"
|
||||
>
|
||||
<div class="pf-c-progress__description">
|
||||
${msg("Internal user usage")}
|
||||
</div>
|
||||
<div class="pf-c-progress__status" aria-hidden="true">
|
||||
<span class="pf-c-progress__measure"
|
||||
>${msg(
|
||||
str`${internalUserPercentage < Infinity ? internalUserPercentage : "∞"}%`,
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-progress__bar"
|
||||
role="progressbar"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="${internalUserPercentage}"
|
||||
>
|
||||
<div
|
||||
class="pf-c-progress__indicator"
|
||||
style="width:${Math.min(internalUserPercentage, 100)}%;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-progress ${externalUserPercentage > 100
|
||||
? "pf-m-danger"
|
||||
: ""} ${externalUserPercentage >= 80 ? "pf-m-warning" : ""}"
|
||||
id="externalUsers"
|
||||
>
|
||||
<div class="pf-c-progress__description">
|
||||
${msg("External user usage")}
|
||||
</div>
|
||||
<div class="pf-c-progress__status" aria-hidden="true">
|
||||
<span class="pf-c-progress__measure"
|
||||
>${msg(
|
||||
str`${externalUserPercentage < Infinity ? externalUserPercentage : "∞"}%`,
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-progress__bar"
|
||||
role="progressbar"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="${externalUserPercentage < Infinity
|
||||
? externalUserPercentage
|
||||
: "∞"}"
|
||||
>
|
||||
<div
|
||||
class="pf-c-progress__indicator"
|
||||
style="width:${Math.min(externalUserPercentage, 100)}%;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ak-enterprise-status-card": EnterpriseStatusCard;
|
||||
}
|
||||
}
|
||||
@ -17,11 +17,10 @@ describe("Search select: Test Input Field", () => {
|
||||
let select: AkSearchSelectViewDriver;
|
||||
|
||||
beforeEach(async () => {
|
||||
await render(
|
||||
render(
|
||||
html`<ak-search-select-view .options=${longGoodForYouPairs}> </ak-search-select-view>`,
|
||||
document.body,
|
||||
);
|
||||
// @ts-ignore
|
||||
select = await AkSearchSelectViewDriver.build(await $("ak-search-select-view"));
|
||||
});
|
||||
|
||||
@ -57,6 +56,7 @@ describe("Search select: Test Input Field", () => {
|
||||
expect(await select.open).toBe(false);
|
||||
expect(await select.menuIsVisible()).toBe(false);
|
||||
await browser.keys("A");
|
||||
select = await AkSearchSelectViewDriver.build(await $("ak-search-select-view"));
|
||||
expect(await select.open).toBe(true);
|
||||
expect(await select.menuIsVisible()).toBe(true);
|
||||
});
|
||||
@ -64,19 +64,19 @@ describe("Search select: Test Input Field", () => {
|
||||
it("should update the list as the user types", async () => {
|
||||
await select.focusOnInput();
|
||||
await browser.keys("Ap");
|
||||
expect(await select.menuIsVisible()).toBe(true);
|
||||
await expect(await select.menuIsVisible()).toBe(true);
|
||||
const elements = Array.from(await select.listElements());
|
||||
expect(elements.length).toBe(2);
|
||||
await expect(elements.length).toBe(2);
|
||||
});
|
||||
|
||||
it("set the value when a match is close", async () => {
|
||||
await select.focusOnInput();
|
||||
await browser.keys("Ap");
|
||||
expect(await select.menuIsVisible()).toBe(true);
|
||||
await expect(await select.menuIsVisible()).toBe(true);
|
||||
const elements = Array.from(await select.listElements());
|
||||
expect(elements.length).toBe(2);
|
||||
await expect(elements.length).toBe(2);
|
||||
await browser.keys(Key.Tab);
|
||||
expect(await (await select.input()).getValue()).toBe("Apples");
|
||||
await expect(await (await select.input()).getValue()).toBe("Apples");
|
||||
});
|
||||
|
||||
it("should close the menu when the user clicks away", async () => {
|
||||
@ -93,8 +93,8 @@ describe("Search select: Test Input Field", () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await document.body.querySelector("#a-separate-component")?.remove();
|
||||
await document.body.querySelector("ak-search-select-view")?.remove();
|
||||
document.body.querySelector("#a-separate-component")?.remove();
|
||||
document.body.querySelector("ak-search-select-view")?.remove();
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
if (document.body["_$litPart$"]) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"baseUrl": ".",
|
||||
"types": ["node", "webdriverio/async", "@wdio/cucumber-framework", "expect-webdriverio"],
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import type { Options } from "@wdio/types";
|
||||
import { cwd } from "process";
|
||||
// @ts-ignore
|
||||
import * as postcssLit from "rollup-plugin-postcss-lit";
|
||||
import postcssLit from "rollup-plugin-postcss-lit";
|
||||
import type { UserConfig } from "vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
@ -31,7 +30,6 @@ export const config: Options.Testrunner = {
|
||||
"preventAssignment": true,
|
||||
}),
|
||||
...(config?.plugins ?? []),
|
||||
// @ts-ignore
|
||||
postcssLit(),
|
||||
tsconfigPaths(),
|
||||
],
|
||||
@ -92,14 +90,13 @@ export const config: Options.Testrunner = {
|
||||
capabilities: [
|
||||
{
|
||||
// capabilities for local browser web tests
|
||||
browserName: "chrome", // or "firefox", "microsoftedge", "safari"
|
||||
...(runHeadless
|
||||
? {
|
||||
"goog:chromeOptions": {
|
||||
args: ["headless", "disable-gpu"],
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
"browserName": "chrome", // or "firefox", "microsoftedge", "safari"
|
||||
"goog:chromeOptions": {
|
||||
args: [
|
||||
"disable-search-engine-choice-screen",
|
||||
...(runHeadless ? ["headless", "disable-gpu"] : []),
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
Reference in New Issue
Block a user