From 5d52038afc4eef13bdada6a57ca7a836da5a5b89 Mon Sep 17 00:00:00 2001 From: Ken Sternberg Date: Fri, 4 Aug 2023 14:06:09 -0700 Subject: [PATCH] web: display tests for CryptoCertificateKeypair search This adds a Storybook for the CryptoCertificateKeypair search, including a mock fetch of the data. In the course of running the tests, we discovered that including the SearchSelect _class_ won't include the customElement declaration unless you include the whole file! Other bugs found: including the CSS from Storybook is different from that of LitElement native, so much so that the adapter needed to be included. FlowSearch had a similar bug. The problem only manifests when building via Webpack (which Storybook uses) and not Rollup, but we should support both in distribution. --- .../common/ak-crypto-certificate-search.ts | 5 +- .../admin/common/ak-flow-search/FlowSearch.ts | 1 + .../ak-crypto-certificate-search.stories.ts | 91 +++++++++++++++++++ web/src/admin/common/stories/samples.ts | 29 ++++++ web/src/elements/forms/SearchSelect.ts | 3 +- 5 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 web/src/admin/common/stories/ak-crypto-certificate-search.stories.ts create mode 100644 web/src/admin/common/stories/samples.ts diff --git a/web/src/admin/common/ak-crypto-certificate-search.ts b/web/src/admin/common/ak-crypto-certificate-search.ts index 3612b722b..15fe347c2 100644 --- a/web/src/admin/common/ak-crypto-certificate-search.ts +++ b/web/src/admin/common/ak-crypto-certificate-search.ts @@ -1,6 +1,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKElement } from "@goauthentik/elements/Base"; import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect"; +import "@goauthentik/elements/forms/SearchSelect"; import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; import { html } from "lit"; @@ -29,7 +30,7 @@ const renderValue = (item: CertificateKeyPair | undefined): string | undefined = */ @customElement("ak-crypto-certificate-search") -export class CryptoCertificateSearch extends CustomListenerElement(AKElement) { +export class AkCryptoCertificateSearch extends CustomListenerElement(AKElement) { @property({ type: String, reflect: true }) certificate?: string; @@ -126,4 +127,4 @@ export class CryptoCertificateSearch extends CustomListenerElement(AKElement) { } } -export default CryptoCertificateSearch; +export default AkCryptoCertificateSearch; diff --git a/web/src/admin/common/ak-flow-search/FlowSearch.ts b/web/src/admin/common/ak-flow-search/FlowSearch.ts index 484df6d6e..b19a8e4cf 100644 --- a/web/src/admin/common/ak-flow-search/FlowSearch.ts +++ b/web/src/admin/common/ak-flow-search/FlowSearch.ts @@ -2,6 +2,7 @@ import { RenderFlowOption } from "@goauthentik/admin/flows/utils"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { AKElement } from "@goauthentik/elements/Base"; import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect"; +import "@goauthentik/elements/forms/SearchSelect"; import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; import { html } from "lit"; diff --git a/web/src/admin/common/stories/ak-crypto-certificate-search.stories.ts b/web/src/admin/common/stories/ak-crypto-certificate-search.stories.ts new file mode 100644 index 000000000..ac31ec2b9 --- /dev/null +++ b/web/src/admin/common/stories/ak-crypto-certificate-search.stories.ts @@ -0,0 +1,91 @@ +import "@goauthentik/elements/forms/HorizontalFormElement"; +import "@goauthentik/elements/messages/MessageContainer"; +import { Meta } from "@storybook/web-components"; + +import { TemplateResult, html } from "lit"; + +import "../ak-crypto-certificate-search"; +import AkCryptoCertificateSearch from "../ak-crypto-certificate-search"; +import { dummyCryptoCertsSearch } from "./samples"; + +const metadata: Meta = { + title: "Components / Searches / CryptoCertificateKeyPair", + component: "ak-crypto-certificate-search", + parameters: { + docs: { + description: { + component: "A search function for cryptographic certificates in Authentik", + }, + }, + mockData: [ + { + url: "/api/v3/crypto/certificatekeypairs/?has_key=true&include_details=false&ordering=name", + method: "GET", + status: 200, + response: dummyCryptoCertsSearch, + }, + ], + }, + argTypes: { + // Typescript is unaware that arguments for components are treated as properties, and + // properties are typically renamed to lower case, even if the variable is not. + // @ts-expect-error + nokey: { + control: "boolean", + description: + "When true, certificates without valid keys will be included in the search", + }, + singleton: { + control: "boolean", + description: + "Supports the SAML Source search: when true, if there is no certificate in the current form and there is one and only one certificate in the Authentik database, use that certificate by default.", + }, + }, +}; + +export default metadata; + +const LIGHT = "pf-t-light"; +function injectTheme() { + setTimeout(() => { + if (!document.body.classList.contains(LIGHT)) { + document.body.classList.add(LIGHT); + } + }); +} + +const container = (testItem: TemplateResult) => { + injectTheme(); + return html`
+ + + ${testItem} +

+    
`; +}; + +export const CryptoCertificateSearch = () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const showMessage = (ev: CustomEvent) => { + const detail = ev.detail; + delete detail["target"]; + document.getElementById("message-pad")!.innerText = `Event: ${JSON.stringify( + detail, + null, + 2, + )}`; + }; + + return container( + html` + `, + ); +}; diff --git a/web/src/admin/common/stories/samples.ts b/web/src/admin/common/stories/samples.ts new file mode 100644 index 000000000..10e683d6e --- /dev/null +++ b/web/src/admin/common/stories/samples.ts @@ -0,0 +1,29 @@ +export const dummyCryptoCertsSearch = { + pagination: { + next: 0, + previous: 0, + count: 1, + current: 1, + total_pages: 1, + start_index: 1, + end_index: 1, + }, + + results: [ + { + pk: "63efd1b8-6c39-4f65-8157-9a406cb37447", + name: "authentik Self-signed Certificate", + fingerprint_sha256: null, + fingerprint_sha1: null, + cert_expiry: null, + cert_subject: null, + private_key_available: true, + private_key_type: null, + certificate_download_url: + "/api/v3/crypto/certificatekeypairs/63efd1b8-6c39-4f65-8157-9a406cb37447/view_certificate/?download", + private_key_download_url: + "/api/v3/crypto/certificatekeypairs/63efd1b8-6c39-4f65-8157-9a406cb37447/view_private_key/?download", + managed: null, + }, + ], +}; diff --git a/web/src/elements/forms/SearchSelect.ts b/web/src/elements/forms/SearchSelect.ts index 02d1f1fc2..0aba008ee 100644 --- a/web/src/elements/forms/SearchSelect.ts +++ b/web/src/elements/forms/SearchSelect.ts @@ -1,5 +1,6 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { ascii_letters, digits, groupBy, randomString } from "@goauthentik/common/utils"; +import { adaptCSS } from "@goauthentik/common/utils"; import { AKElement } from "@goauthentik/elements/Base"; import { PreventFormSubmit } from "@goauthentik/elements/forms/Form"; import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; @@ -75,7 +76,7 @@ export class SearchSelect extends CustomEmitterElement(AKElement) { constructor() { super(); if (!document.adoptedStyleSheets.includes(PFDropdown)) { - document.adoptedStyleSheets = [...document.adoptedStyleSheets, PFDropdown]; + document.adoptedStyleSheets = adaptCSS([...document.adoptedStyleSheets, PFDropdown]); } this.dropdownContainer = document.createElement("div"); this.observer = new IntersectionObserver(() => {