Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full replacement of VSCodeDropdown & VSCodeOption with custom react elements [Webview-UI-toolkit deprecation] #1276

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions webview-ui/src/AzureServiceOperator/AzureServiceOperator.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react";
import styles from "./AzureServiceOperator.module.css";
import { InitialState } from "../../../src/webview-contract/webviewDefinitions/azureServiceOperator";
import { useEffect } from "react";
Expand Down Expand Up @@ -64,7 +63,7 @@ export function AzureServiceOperator(initialState: InitialState) {
<p>
The Azure Service Operator helps you provision Azure resources and connect your applications to them
from within Kubernetes.
<VSCodeLink href="https://aka.ms/aks/aso">&nbsp;Learn more</VSCodeLink>
<a href="https://aka.ms/aks/aso">&nbsp;Learn more</a>
</p>
<div className={styles.content}>
<div className={styles.inputPane}>
Expand Down
50 changes: 20 additions & 30 deletions webview-ui/src/AzureServiceOperator/Inputs.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
import {
VSCodeButton,
VSCodeDropdown,
VSCodeLink,
VSCodeOption,
VSCodeTextField,
} from "@vscode/webview-ui-toolkit/react";
import styles from "./AzureServiceOperator.module.css";
import { ASOState, EventDef, InstallStepStatus } from "./helpers/state";
import { EventHandlers } from "../utilities/state";
Expand All @@ -14,6 +7,8 @@ import { MessageSink } from "../../../src/webview-contract/messaging";
import { getRequiredInputs } from "./helpers/inputs";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { CustomDropdown } from "../components/CustomDropdown";
import { CustomDropdownOption } from "../components/CustomDropdownOption";

type ChangeEvent = Event | FormEvent<HTMLElement>;

Expand Down Expand Up @@ -45,10 +40,8 @@ export function Inputs(props: InputsProps) {
props.handlers.onSetCheckingSP();
}

function handleSubscriptionChanged(e: Event | FormEvent<HTMLElement>) {
const elem = e.target as HTMLInputElement;
const subscriptionId = elem.value || null;
props.handlers.onSetSelectedSubscriptionId(subscriptionId);
function handleSubscriptionChanged(value: string) {
props.handlers.onSetSelectedSubscriptionId(value);
}

function handleSubmit(e: FormEvent) {
Expand All @@ -74,30 +67,30 @@ export function Inputs(props: InputsProps) {
<FontAwesomeIcon className={styles.infoIndicator} icon={faInfoCircle} />
Provide the App ID and password of a Service Principal with Contributor permissions for your
subscription. This allows ASO to create resources in your subscription on your behalf.
<VSCodeLink href="https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli">
<a href="https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli">
&nbsp; Learn more
</VSCodeLink>
</a>
</p>

<label htmlFor="spappid" className={styles.label}>
Enter App ID of service principal:
</label>
<VSCodeTextField
<input
type="text"
value={appId || ""}
readOnly={!canEditSP}
required
id="spappid"
onInput={handleAppIdChange}
className={styles.control}
size={50}
type="text"
placeholder="e.g. 041ccd53-e72f-45d1-bbff-382c82f6f9a1"
/>

<label htmlFor="spcred" className={styles.label}>
Enter Password of Service Principal:
</label>
<VSCodeTextField
<input
value={appSecret || ""}
readOnly={!canEditSP}
required
Expand All @@ -109,9 +102,9 @@ export function Inputs(props: InputsProps) {
placeholder="Service principal password"
/>

<VSCodeButton disabled={!canCheckSP} onClick={handleCheckSPClick}>
<button disabled={!canCheckSP} onClick={handleCheckSPClick}>
Check
</VSCodeButton>
</button>
</div>
{canViewSubscriptions && (
<div className={styles.inputContainer}>
Expand All @@ -121,31 +114,28 @@ export function Inputs(props: InputsProps) {
The supplied service principal has some role assignments on the following subscriptions. Please
ensure these are adequate for the Azure resources that ASO will be creating in your selected
subscription.
<VSCodeLink href="https://azure.github.io/azure-service-operator/#installation">
&nbsp; Learn more
</VSCodeLink>
<a href="https://azure.github.io/azure-service-operator/#installation">&nbsp; Learn more</a>
</p>

<label htmlFor="sub-select" className={styles.label}>
Subscription for ASO resources:
</label>
<VSCodeDropdown

<CustomDropdown
id="sub-select"
value={selectedSubscription?.id || ""}
className={styles.control}
disabled={!canSelectSubscription}
onChange={handleSubscriptionChanged}
className={styles.control}
>
{subscriptions.length !== 1 && <VSCodeOption value="">Select</VSCodeOption>}
{subscriptions.length !== 1 && <CustomDropdownOption value="" label="Select" />}
{subscriptions.map((s) => (
<VSCodeOption value={s.id} key={s.id}>
{s.name}
</VSCodeOption>
<CustomDropdownOption value={s.id} key={s.id} label={s.name} />
))}
</VSCodeDropdown>
<VSCodeButton disabled={!canStartInstalling} type="submit">
</CustomDropdown>
<button disabled={!canStartInstalling} type="submit">
Install
</VSCodeButton>
</button>
</div>
)}
</form>
Expand Down
8 changes: 6 additions & 2 deletions webview-ui/src/CreateCluster/CreateCluster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ export function CreateCluster(initialState: InitialState) {
/>
);
case Stage.Creating:
console.log("Creating cluster");
return (
<>
<h3>
Creating Cluster {state.createParams!.name} in {state.createParams!.location}
Creating Cluster {state.createParams?.name} in {state.createParams?.location}
</h3>
{state.deploymentPortalUrl && (
<p>
Expand All @@ -62,7 +63,10 @@ export function CreateCluster(initialState: InitialState) {
);
case Stage.Succeeded:
return (
<Success portalClusterUrl={state.createdCluster?.portalUrl || ""} name={state.createParams!.name} />
<Success
portalClusterUrl={state.createdCluster?.portalUrl || ""}
name={state.createParams?.name || ""}
/>
);
default:
throw new Error(`Unexpected stage ${state.stage}`);
Expand Down
79 changes: 40 additions & 39 deletions webview-ui/src/CreateCluster/CreateClusterInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { faTimesCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
// import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
import { FormEvent, useState } from "react";
import { MessageSink } from "../../../src/webview-contract/messaging";
import {
Expand All @@ -16,9 +16,9 @@ import styles from "./CreateCluster.module.css";
import { CreateClusterPresetInput } from "./CreateClusterPresetInput";
import { CreateResourceGroupDialog } from "./CreateResourceGroup";
import { EventDef } from "./helpers/state";

type ChangeEvent = Event | FormEvent<HTMLElement>;

import { CustomDropdown } from "../components/CustomDropdown";
import { CustomDropdownOption } from "../components/CustomDropdownOption";
import { MouseEvent } from "react";
interface CreateClusterInputProps {
locations: string[];
resourceGroups: ResourceGroup[];
Expand All @@ -32,7 +32,7 @@ export function CreateClusterInput(props: CreateClusterInputProps) {
const [isNewResourceGroupDialogShown, setIsNewResourceGroupDialogShown] = useState(false);
const [newResourceGroupName, setNewResourceGroupName] = useState<string | null>(null);
const [presetSelected, setPresetSelected] = useState<PresetType>(PresetType.Automatic);
const [selectedIndex, setSelectedIndex] = useState<number>(0);
const [selectedResourceGroup, setSelectedResourceGroup] = useState<string>("");
const [location, setLocation] = useState<Validatable<string>>(unset());

const newResourceGroup = newResourceGroupName
Expand All @@ -51,26 +51,30 @@ export function CreateClusterInput(props: CreateClusterInputProps) {
setIsNewResourceGroupDialogShown(false);
setExistingResourceGroup(valid(null));
setNewResourceGroupName(groupName);
setSelectedIndex(1); // this is the index of the new resource group and the first option is "Select"
setSelectedResourceGroup(groupName);
}

function handlePresetSelection(presetSelected: PresetType) {
setPresetSelected(presetSelected);
}

function handleValidationAndIndex(e: ChangeEvent) {
handleExistingResourceGroupChange(e);
const ele = e.currentTarget as HTMLSelectElement;
setSelectedIndex(ele.selectedIndex);
function handleValidationAndIndex(value: string) {
handleExistingResourceGroupChange(value);
setSelectedResourceGroup(value);
}

function handleExistingResourceGroupChange(e: ChangeEvent) {
const elem = e.currentTarget as HTMLSelectElement;
const resourceGroup = elem.selectedIndex <= 0 ? null : allResourceGroups[elem.selectedIndex - 1];
function handleExistingResourceGroupChange(value: string) {
const resourceGroup = value ? allResourceGroups.find((group) => group.name === value) : null;
const validatable = resourceGroup ? valid(resourceGroup) : invalid(null, "Resource Group is required.");
setExistingResourceGroup(validatable);
}

const handleCreateNewRG = (event: MouseEvent<HTMLButtonElement>) => {
setIsNewResourceGroupDialogShown(true);
event.preventDefault();
event.stopPropagation();
};

function getValidatedName(name: string): Validatable<string> {
if (!name) return invalid(name, "Cluster name must be at least 1 character long.");
if (name.length > 63) return invalid(name, "Cluster name must be at most 63 characters long.");
Expand All @@ -84,15 +88,14 @@ export function CreateClusterInput(props: CreateClusterInputProps) {
return valid(name);
}

function handleNameChange(e: ChangeEvent) {
const name = (e.currentTarget as HTMLInputElement).value;
function handleNameChange(e: FormEvent<HTMLInputElement>) {
const name = e.currentTarget.value;
const validated = getValidatedName(name);
setName(validated);
}

function handleLocationChange(e: ChangeEvent) {
const elem = e.currentTarget as HTMLSelectElement;
const location = elem.selectedIndex <= 0 ? null : props.locations[elem.selectedIndex - 1];
function handleLocationChange(value: string) {
const location = value ? props.locations.find((loc) => loc === value) : null;
const validated = location ? valid(location) : missing<string>("Location is required.");
setLocation(validated);
}
Expand Down Expand Up @@ -139,32 +142,32 @@ export function CreateClusterInput(props: CreateClusterInputProps) {
<label htmlFor="cluster-details" className={styles.clusterDetailsLabel}>
Cluster details
</label>

<label htmlFor="existing-resource-group-dropdown" className={styles.label}>
Resource Group*
</label>
<VSCodeDropdown
<CustomDropdown
id="existing-resource-group-dropdown"
className={styles.midControl}
onBlur={handleValidationAndIndex}
value={selectedResourceGroup}
onChange={handleValidationAndIndex}
selectedIndex={selectedIndex}
disabled={false}
aria-label="Select a resource group"
>
<VSCodeOption selected value="">
Select
</VSCodeOption>
<CustomDropdownOption value="" label="Select" />
{allResourceGroups.length > 0 ? (
allResourceGroups.map((group) => (
<VSCodeOption key={group.name} value={group.name}>
{group === newResourceGroup ? "(New)" : ""} {group.name}
</VSCodeOption>
<CustomDropdownOption
key={group.name}
value={group.name}
label={`${group === newResourceGroup ? "(New) " : ""}${group.name}`}
/>
))
) : (
<VSCodeOption disabled>No resource groups available</VSCodeOption>
<CustomDropdownOption value="" label="No resource groups available" />
)}
</VSCodeDropdown>
</CustomDropdown>

<button className={styles.sideControl} onClick={() => setIsNewResourceGroupDialogShown(true)}>
<button className={styles.sideControl} onClick={handleCreateNewRG}>
Create New
</button>
{hasMessage(existingResourceGroup) && (
Expand Down Expand Up @@ -195,19 +198,17 @@ export function CreateClusterInput(props: CreateClusterInputProps) {
<label htmlFor="location-dropdown" className={styles.label}>
Region*
</label>
<VSCodeDropdown
<CustomDropdown
id="location-dropdown"
className={styles.longControl}
onBlur={handleLocationChange}
value={isValueSet(location) ? location.value : ""}
onChange={handleLocationChange}
disabled={false}
>
<VSCodeOption value="">Select</VSCodeOption>
<CustomDropdownOption value="" label="Select" />
{props.locations.map((location) => (
<VSCodeOption key={location} value={location}>
{location}
</VSCodeOption>
<CustomDropdownOption key={location} value={location} label={location} />
))}
</VSCodeDropdown>
</CustomDropdown>
{hasMessage(location) && (
<span className={styles.validationMessage}>
<FontAwesomeIcon className={styles.errorIndicator} icon={faTimesCircle} />
Expand Down
Loading