vscode icon indicating copy to clipboard operation
vscode copied to clipboard

INVALID tree item

Open lgund opened this issue 3 years ago • 1 comments

Does this issue occur when all extensions are disabled?: Yes

  • VS Code Version: 1.70.0
  • OS Version: Windows 11 22H2

Steps to Reproduce:

Currently I write a vscode extension with a sidebar provider. Inside the provider I set icons in front of the tree. After I updated VSCode today I got the message inside the console:

INVALID tree item, invalid iconPath {dark: 'f:\VisualStudioCode\vscode-devops-extension\resources\icons\preview-light.svg', light: 'f:\VisualStudioCode\vscode-devops-extension\resources\icons\preview-dark.svg'}
arg1:
{dark: 'f:\\VisualStudioCode\\vscode-devops-extension\\resources\\icons\\preview-light.svg', light: 'f:\\VisualStudioCode\\vscode-devops-extension\\resources\\icons\\preview-dark.svg'}
dark:
'f:\\VisualStudioCode\\vscode-devops-extension\\resources\\icons\\preview-light.svg'
light:
'f:\\VisualStudioCode\\vscode-devops-extension\\resources\\icons\\preview-dark.svg'
[[Prototype]]:
Object
Extension first-coder.manager-devops has provided an invalid tree item.

But the tree and the icons are loaded:

image

Here is my source of the provider:

import * as vscode from "vscode";
import * as path from "path";
import { from } from "linq-to-typescript";
import { BaseTreeProvider } from "./BaseTreeProvider";
import { BoardTreeItem } from "./BoardTreeItem";
import { ProjectTreeProvider } from "./ProjectTreeProvider";
import { CommentService, ICommentFiles } from "../Service/CommentService";
import { ITicket } from "../Models/Git/ITicket";
import { IProject } from "../Models/Git/IProject";
import { GitUtils } from "../Utils/GitUtils";
import { IBoardColumn } from "../Models/Git/IBoardColumn";

interface ICommentTicket {
  filePath: string;
  id: number;
  childItems: BoardTreeItem[] | undefined;
}

export class BoardTreeProvider extends BaseTreeProvider {
  private project: IProject | undefined;
  private team: string | undefined;
  private commentFiles: ICommentFiles[] | null = null;
  private tickets: ITicket[] | undefined;
  private boardItems: BoardTreeItem[] = [];
  private isLoading = false;

  constructor(
    context: vscode.ExtensionContext,
    projectTree: ProjectTreeProvider,
    private commentService: CommentService
  ) {
    super(context);
    projectTree.onSelectedProject.subscribe(
      async (project: IProject | undefined) => {
        if (this.isLoading) {
          vscode.window.showErrorMessage(`Konnte neues Tickets für das Projekt ${project?.name} nicht laden, da noch Tickets von vorherigen 
          Projekt geladen werden. Bitte versuchen Sie es nochmal, sobald die Tickets vom bisherigen Projekt geladen sind.`);
          return;
        }
        this.isLoading = true;
        this.project = project;
        this.boardItems = [];

        if (this.project === undefined) {
          vscode.window.showErrorMessage(
            "Projekt konnte nicht geladen werden. Bitte erneut versuchen.",
          );
          return;
        }

        const projectName = this.project.name;
        this.team = await GitUtils.selectTeam(
          await this.gitService?.getTeams(projectName),
          projectName,
        );
        if (this.team === undefined) {
          return;
        }

        
        this.refreshTree(projectName);
      },
    );
  }

  /**
   * Extend the tree refresh function
   */
  public async refreshTree(projectName: string) {
    if (this.team !== undefined) {
      this.tickets = await this.gitService?.getTickets(
        projectName,
        this.team,
      );
  
      this.commentFiles = this.commentService.searchFiles();
    }
    super.refresh();
  }

  /**
     * Register commands to vscode
     * @param context Extension context
     */
  protected registerCommands(context: vscode.ExtensionContext): void {
    context.subscriptions.push(
      vscode.commands.registerCommand("manager-devops.refreshTickets", () => {
        if (this.project?.name === undefined) {
          return;
        }
        this.boardItems = [];
        this.refreshTree(this.project.name);
      })
    );
  }

  /**
   * Get the tickets and the child items to jump to the ticket in the comments
   * @param tickets Tickets from gitservice api
   * @returns Treeitems for the tree
   */
  getTickets(tickets: ITicket[] | undefined, boardType: string): BoardTreeItem[] {
    let textTickets: ICommentTicket[] = [];
    let items: BoardTreeItem[] = [];

    if (tickets === undefined) {
      return items;
    }
    
    // Find tickets in source
    if (this.commentFiles !== null) {
      for (const file of this.commentFiles) {
        for (const line of file.lines) {
          const ticketInfo = this.commentService.parseTicketline(line.text);
          let foundTicket = from(textTickets).firstOrDefault(
            (x: ICommentTicket) => x.id === ticketInfo?.id,
          );
          if (foundTicket !== null) {
            foundTicket.childItems?.push({
              label: line.text.trim(),
              children: undefined,
              project: this.project,
              iconPath: {
                dark: path.join(
                  __filename,
                  "..",
                  "..",
                  "resources",
                  "icons",
                  "code-light.svg",
                ),
                light: path.join(
                  __filename,
                  "..",
                  "..",
                  "resources",
                  "icons",
                  "code-dark.svg",
                ),
              },
            });
          } else {
            textTickets.push({
              filePath: file.path,
              id: Number(ticketInfo?.id),
              childItems: [
                {
                  command: {
                    command: "manager-devops-internal.openFileAtLocation",
                    title: "Datei öffnen",
                    arguments: [file.path, line.start + 1],
                  },
                  label: line.text.trim(),
                  children: undefined,
                  project: this.project,
                  iconPath: {
                    dark: path.join(
                      __filename,
                      "..",
                      "..",
                      "resources",
                      "icons",
                      "code-light.svg",
                    ),
                    light: path.join(
                      __filename,
                      "..",
                      "..",
                      "resources",
                      "icons",
                      "code-dark.svg",
                    ),
                  },
                },
              ],
            });
          }
        }
      }
    }

    for (const ticket of tickets) {
      const textTicket = from(textTickets).firstOrDefault(
        (x: ICommentTicket) => x.id === ticket.id,
      );

      items.push({
        label: "#" + ticket.id + " " + ticket.title,
        id: ticket.id.toString(),
        contextValue: "ticket",
        team: this.team,
        board: boardType,
        collapsibleState:
          textTicket !== null
            ? vscode.TreeItemCollapsibleState.Collapsed
            : vscode.TreeItemCollapsibleState.None,
        iconPath: {
          dark: path.join(
            __filename,
            "..",
            "..",
            "resources",
            "icons",
            "preview-light.svg",
          ),
          light: path.join(
            __filename,
            "..",
            "..",
            "resources",
            "icons",
            "preview-dark.svg",
          ),
        },
        tooltip: ticket.board,
        description:
          "Created on: " +
          ticket.created.toLocaleDateString() +
          "\nCommentcount: " +
          ticket.comments,
        children: textTicket?.childItems,
        project: this.project,
      });
    }
    return items;
  }

  /**
   * Create the board column tree
   * @param boardColumns columns of board from gitservice
   * @param boardType The type of the board
   * @returns The board tree items of columns
   */
  async getBoardsColumns(
    boardColumns: IBoardColumn[] | undefined,
    boardType: string
  ): Promise<BoardTreeItem[]> {
    let items: BoardTreeItem[] = [];
    if (boardColumns === undefined || boardColumns.length === 0) {
      return items;
    }

    const projectName = this.project?.name;
    if (this.team === undefined || projectName === undefined) {
      return items;
    }

    for (const boardColumn of boardColumns) {
      // Need to add an s cause devops ist stupid
      let boardTickets = this.tickets?.filter((t) => t.board === boardColumn.name && t.type + "s" === boardType);
      items.push({
        label: boardColumn.name,
        id: boardColumn.id,
        description: boardColumn.id,
        iconPath: {
          dark: path.join(
            __filename,
            "..",
            "..",
            "resources",
            "icons",
            "browser-light.svg",
          ),
          light: path.join(
            __filename,
            "..",
            "..",
            "resources",
            "icons",
            "browser-dark.svg",
          ),
        },
        children: this.getTickets(boardTickets, boardType),
        collapsibleState:
          boardTickets !== undefined && boardTickets.length > 0
            ? vscode.TreeItemCollapsibleState.Collapsed
            : vscode.TreeItemCollapsibleState.None,
        project: this.project,
      });
    }
    return items;
  }

  /**
   * Get the tree
   * @param element The elements that are already created on the tree
   * @returns The whole tree
   */
  async getChildren(
    element?: BoardTreeItem | undefined,
  ): Promise<BoardTreeItem[] | undefined> {
    let items: BoardTreeItem[] = [];
    if (this.project === undefined) {
      this.isLoading = false;
      return items;
    }

    const projectName = this.project.name;
    if (this.team === undefined) {
      this.isLoading = false;
      return items;
    }

    const boards = await this.gitService?.getBoards(projectName, this.team);
    if (boards === undefined) {
      this.isLoading = false;
      return items;
    }

    if (element === undefined) {
      for (const board of boards) {
        const boardColumns = await this.gitService?.getColumns(
          projectName,
          this.team,
          board.name,
        );
        this.boardItems.push({
          label: board.name,
          id: board.id.toString(),
          description: board.id.toString(),
          iconPath: {
            dark: path.join(
              __filename,
              "..",
              "..",
              "resources",
              "icons",
              "table-light.svg",
            ),
            light: path.join(
              __filename,
              "..",
              "..",
              "resources",
              "icons",
              "table-dark.svg",
            ),
          },
          children: await this.getBoardsColumns(boardColumns, board.name),
          collapsibleState:
            boardColumns !== undefined && boardColumns.length > 0
              ? vscode.TreeItemCollapsibleState.Collapsed
              : vscode.TreeItemCollapsibleState.None,
          project: this.project,
        });
      }
      this.isLoading = false;
      return this.boardItems;
    }

    this.isLoading = false;
    return element?.children;
  }
}

lgund avatar Aug 05 '22 14:08 lgund

@lgund thank you for reporting this! I added a check for malformed tree items a few weeks ago, and it looks like I made a mistake in the check. This is a harmless log message, but it is annoying. If we have a 1.70.2 release we will include the fix there.

alexr00 avatar Aug 10 '22 12:08 alexr00

To verify you will need to run an extension that uses a { light: Uri; dark: Uri } for a tree item and doesn't new the tree item. An easy way to verify with this setup is the following:

  1. Clone the vscode-extension-samples repository and open the tree-view-sample.
  2. Change this line to create the vscode.TreeItem using { label: .... instead of new: https://github.com/microsoft/vscode-extension-samples/blob/d6ed4aeb1ce1296a0c2fc882de4275da7041a54d/tree-view-sample/src/jsonOutline.ts#L118-L119
  3. Run the sample and open the developer tools.
  4. Open a json file with the running sample.
  5. Verify that the Json Outline view works. Verify that there are no "INVALID tree item" logs in the developer console.

alexr00 avatar Aug 17 '22 07:08 alexr00

Verified by @hediet

alexr00 avatar Aug 17 '22 12:08 alexr00