web: reify the data loop

I was very unhappy with the "update this dot-path" mechanism I was using earlier; it was hard
for me to read and understand what was happening, and I wrote the darned thing.  I decided instead
to go with a hard substitution model; each phase of the wizard is responsible for updating the
*entire* payload, mostly by creating a new payload and substituting the field value associated
with the event.

On the receiver, we have to do that *again* to handle the swapping of providers when the user
chooses one and then another.  It looks clunky, and it is, but it's *legible*; a junior dev
could understand what it's doing, and that's the goal.
This commit is contained in:
Ken Sternberg 2023-10-18 08:58:50 -07:00
parent 5e1854f74e
commit 09fedcacf0
9 changed files with 29 additions and 9 deletions

View File

@ -2,7 +2,7 @@ import { WizardPanel } from "@goauthentik/components/ak-wizard-main/types";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter"; import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
import { consume } from "@lit/context"; import { consume } from "@lit-labs/context";
import { query } from "@lit/reactive-element/decorators.js"; import { query } from "@lit/reactive-element/decorators.js";
import { styles as AwadStyles } from "./BasePanel.css"; import { styles as AwadStyles } from "./BasePanel.css";

View File

@ -1,4 +1,4 @@
import { createContext } from "@lit/context"; import { createContext } from "@lit-labs/context";
import { ApplicationWizardState } from "./types"; import { ApplicationWizardState } from "./types";

View File

@ -1,8 +1,7 @@
import { merge } from "@goauthentik/common/merge";
import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard"; import { AkWizard } from "@goauthentik/components/ak-wizard-main/AkWizard";
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"; import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
import { ContextProvider } from "@lit/context"; import { ContextProvider } from "@lit-labs/context";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { customElement, state } from "lit/decorators.js"; import { customElement, state } from "lit/decorators.js";
@ -97,7 +96,22 @@ export class ApplicationWizard extends CustomListenerElement(
this.requestUpdate(); this.requestUpdate();
} }
this.wizardState = merge(this.wizardState, update) as ApplicationWizardState; // Being extremely explicit about how a wizard state gets built, so that we preserve as much
// information as possible. This is much more predictable than using a generic merge.
this.wizardState = {
...this.wizardState,
app: {
...this.wizardState.app ?? {},
...update.app ?? {}
},
provider: {
...this.wizardState.provider ?? {},
...update.provider ?? {}
},
providerModel: update.providerModel
}
this.wizardStateProvider.setValue(this.wizardState); this.wizardStateProvider.setValue(this.wizardState);
this.requestUpdate(); this.requestUpdate();
} }

View File

@ -27,7 +27,9 @@ export class ApplicationWizardApplicationDetails extends BasePanel {
const value = target.type === "checkbox" ? target.checked : target.value; const value = target.type === "checkbox" ? target.checked : target.value;
this.dispatchWizardUpdate({ this.dispatchWizardUpdate({
update: { update: {
...this.wizard,
app: { app: {
...this.wizard.app,
[target.name]: value, [target.name]: value,
}, },
}, },

View File

@ -25,7 +25,10 @@ export class ApplicationWizardAuthenticationMethodChoice extends BasePanel {
handleChoice(ev: InputEvent) { handleChoice(ev: InputEvent) {
const target = ev.target as HTMLInputElement; const target = ev.target as HTMLInputElement;
this.dispatchWizardUpdate({ this.dispatchWizardUpdate({
update: { providerModel: target.value }, update: {
...this.wizard,
providerModel: target.value
},
status: this.validator() ? "valid" : "invalid", status: this.validator() ? "valid" : "invalid",
}); });
} }

View File

@ -133,7 +133,6 @@ export class ApplicationWizardCommitApplication extends BasePanel {
if (body["provider"] !== undefined) { if (body["provider"] !== undefined) {
errs = [...errs, msg("In the Provider:"), ...spaceify(body["provider"])]; errs = [...errs, msg("In the Provider:"), ...spaceify(body["provider"])];
} }
console.log(body, errs);
return errs; return errs;
} }

View File

@ -10,7 +10,9 @@ export class ApplicationWizardProviderPageBase extends BasePanel {
const value = target.type === "checkbox" ? target.checked : target.value; const value = target.type === "checkbox" ? target.checked : target.value;
this.dispatchWizardUpdate({ this.dispatchWizardUpdate({
update: { update: {
...this.wizard,
provider: { provider: {
...this.wizard.provider,
[target.name]: value, [target.name]: value,
}, },
}, },

View File

@ -1,4 +1,4 @@
import { consume } from "@lit/context"; import { consume } from "@lit-labs/context";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js"; import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { state } from "@lit/reactive-element/decorators/state.js"; import { state } from "@lit/reactive-element/decorators/state.js";
import { LitElement, html } from "lit"; import { LitElement, html } from "lit";

View File

@ -29,7 +29,7 @@ export interface ApplicationWizardState {
type StatusType = "invalid" | "valid" | "submitted" | "failed"; type StatusType = "invalid" | "valid" | "submitted" | "failed";
export type ApplicationWizardStateUpdate = { export type ApplicationWizardStateUpdate = {
update?: Partial<ApplicationWizardState>; update?: ApplicationWizardState;
status?: StatusType; status?: StatusType;
}; };