Repository: hashicorp/vault
File: ui/app/components/namespace-picker.ts (TypeScript)

Code before:
  @tracked searchInputHelpText =
    "Enter a full path in the search bar and hit the 'Enter' ↵ key to navigate faster.";

  constructor(owner: unknown, args: Record<string, never>) {
    super(owner, args);
    this.loadOptions();
    this.fetchManageCapability();
  }

  get allNamespaces(): NamespaceOption[] {
    return this.getOptions(
      this.namespace?.accessibleNamespaces,
      this.namespace?.currentNamespace,
      this.namespace?.path
    );
  }

  get selectedNamespace(): NamespaceOption | null {
    return this.getSelected(this.allNamespaces, this.namespace?.path) ?? null;
  }

  private matchesPath(option: NamespaceOption, currentPath: string): boolean {
    return option?.path === currentPath;
  }

  private getSelected(options: NamespaceOption[], currentPath: string): NamespaceOption | undefined {
    return options.find((option) => this.matchesPath(option, currentPath));
  }

  private getOptions(
    accessibleNamespaces: string[],
    currentNamespace: string,
    path: string
  ): NamespaceOption[] {
    /* Each namespace option has 3 properties: { id, path, and label }
     *   - id: node / namespace name (displayed when the namespace picker is closed)
     *   - path: full namespace path (used to navigate to the namespace)
     *   - label: text displayed inside the namespace picker dropdown (if root, then label = id, else label = path)
     *
     *  Example:
     *   | id       | path           | label          |
     *   | ---      | ----           | -----          |
     *   | 'root'   | ''             | 'root'         |
     *   | 'parent' | 'parent'       | 'parent'       |
     *   | 'child'  | 'parent/child' | 'parent/child' |
     */
    const options = [
      ...(accessibleNamespaces || []).map((ns: string) => {
        const parts = ns.split('/');
        return { id: parts[parts.length - 1] || '', path: ns, label: ns };
      }),

Review comment (suggestion):
The only reason we can't just return a list of `paths` is because the root path is `""` and we want the label to be `root`. 

Even though it's more verbose, it seemed clearer to manage the options as an object rather than manually handling the explicit root case in the dropdown and `onChange` function. This also makes it more extensible if we ever decide to leverage namespace metadata here somehow.

Code after:
    "Enter a full path in the search bar and hit the 'Enter' ↵ key to navigate faster.";

  constructor(owner: unknown, args: Record<string, never>) {
    super(owner, args);
    this.loadOptions();
    this.fetchManageCapability();
  }

  get allNamespaces(): NamespaceOption[] {
    return this.getOptions(this.namespace?.accessibleNamespaces);
  }

  get selectedNamespace(): NamespaceOption | null {
    return this.getSelected(this.allNamespaces, this.namespace?.path) ?? null;
  }

  private matchesPath(option: NamespaceOption, currentPath: string): boolean {
    return option?.path === currentPath;
  }

  private getSelected(options: NamespaceOption[], currentPath: string): NamespaceOption | undefined {
    return options.find((option) => this.matchesPath(option, currentPath));
  }

  private getOptions(accessibleNamespaces: string[]): NamespaceOption[] {
    /* Each namespace option has 2 properties: { path and label }
     *   - path: full namespace path (used to navigate to the namespace)
     *   - label: text displayed inside the namespace picker dropdown (if root, then path is "", else label = path)
     *
     *  Example:
     *   | path           | label          |
     *   | ----           | -----          |
     *   | ''             | 'root'         |
     *   | 'parent'       | 'parent'       |
     *   | 'parent/child' | 'parent/child' |
     */
    const options = (accessibleNamespaces || []).map((ns: string) => ({ path: ns, label: ns }));

    // Add the user's root namespace because `sys/internal/ui/namespaces` does not include it.
    const userRootNamespace = this.auth.authData?.userRootNamespace;
    if (!options?.find((o) => o.path === userRootNamespace)) {
      // the 'root' namespace is technically an empty string so we manually add the 'root' label.
      const label = userRootNamespace === '' ? 'root' : userRootNamespace;
      options.unshift({ path: userRootNamespace, label });
    }

    // If there are no namespaces returned by the internal endpoint, add the current namespace
    // to the list of options. This is a fallback for when the user has access to a single namespace.
    if (options.length === 0) {
      // 'path' defined in the namespace service is the full namespace path
      options.push({ path: this.namespace.path, label: this.namespace.path });