Skip to content

feat: trs #401

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

feat: trs #401

wants to merge 2 commits into from

Conversation

anuragxxd
Copy link
Member

@anuragxxd anuragxxd commented Apr 18, 2025

Description

Fixes #(issue)

Checklist

  • My code follows the contributing guidelines of this project.
  • I am aware that all my commits will be squashed into a single commit, using the PR title as the commit message.
  • I have performed a self-review of my own code.
  • I have commented my code in hard-to-understand areas.
  • I have updated the user-facing documentation to describe any new or changed behavior.
  • My changes generate no new warnings.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have not reduced the existing code coverage.

Comments

Summary by Sourcery

Create a new TRS (Tool Registry Service) web component with enhanced functionality for browsing and filtering tools from GA4GH TRS APIs

New Features:

  • Implement a web component for interacting with GA4GH Tool Registry Service (TRS) API
  • Add support for dynamic pagination of tools
  • Implement advanced filtering and search capabilities for tools
  • Create a detailed view for individual tools with comprehensive information

Enhancements:

  • Refactor package configuration to support modern web component development
  • Improve API interaction with more robust error handling
  • Add TypeScript support for better type safety

Chores:

  • Rename existing TRS package to distinguish from the new implementation
  • Set up project structure for the new web component

Copy link

changeset-bot bot commented Apr 18, 2025

⚠️ No Changeset found

Latest commit: 04cd267

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

sourcery-ai bot commented Apr 18, 2025

Reviewer's Guide by Sourcery

This pull request introduces a new feature for interacting with the GA4GH Tool Registry Service (TRS) API by implementing a new web component using LitElement. It also includes various configuration and setup files for the new package, along with some modifications to existing files to accommodate the new feature.

No diagrams generated as the changes look simple and do not need a visual representation.

File-Level Changes

Change Details Files
Refactor and update the custom-elements-manifest configuration.
  • Removed import of package.json using assert syntax.
  • Replaced direct import with JSON parsing of package.json using fs.readFileSync.
scripts/templates/custom-elements-manifest.config.js
Rename the existing TRS package.
  • Changed the package name from '@elixir-cloud/trs' to '@elixir-cloud/trs-old'.
packages/ecc-client-ga4gh-trs/package.json
Update the base URL in the TRS client HTML file.
  • Changed the baseUrl attribute in the ecc-client-ga4gh-trs component to point to a new API endpoint.
packages/ecc-client-ga4gh-trs/src/index.html
Remove author column from TRS list template.
  • Deleted the table column displaying the author in the TRS list template.
packages/ecc-client-ga4gh-trs/src/components/trs-list/definition/trs-list.template.ts
Introduce a new package for the TRS client using LitElement.
  • Created a new package.json for the LitElement-based TRS client.
  • Added a new API class for interacting with the GA4GH TRS API.
  • Implemented a new LitElement component for displaying TRS tools.
  • Added styles for the new TRS tools component.
  • Included a demo HTML file for the new TRS tools component.
  • Configured web-dev-server and web-test-runner for the new package.
  • Added a README file for the new package.
  • Included a LICENSE file for the new package.
packages/ecc-client-ga4gh-trs-lit/package.json
packages/ecc-client-ga4gh-trs-lit/src/API/trs-api.ts
packages/ecc-client-ga4gh-trs-lit/src/components/tools/tools.ts
packages/ecc-client-ga4gh-trs-lit/src/components/tools/tools.styles.ts
packages/ecc-client-ga4gh-trs-lit/demo/tools/index.html
packages/ecc-client-ga4gh-trs-lit/web-dev-server.config.mjs
packages/ecc-client-ga4gh-trs-lit/web-test-runner.config.mjs
packages/ecc-client-ga4gh-trs-lit/README.md
packages/ecc-client-ga4gh-trs-lit/LICENSE.hbs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!
  • Generate a plan of action for an issue: Comment @sourcery-ai plan on
    an issue to generate a plan of action for it.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

vercel bot commented Apr 18, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
elixir-cloud-components ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 18, 2025 0:57am

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @anuragxxd - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • Hardcoded base URL found in HTML file. (link)

Overall Comments:

  • Consider renaming the PR title to be more descriptive of the changes made, as 'feat: trs' is quite vague.
  • The PR description is missing a summary of the changes and the issue it resolves; consider adding this information for better context.
Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🔴 Security: 1 blocking issue
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

});

const response = await fetch(url);
if (!response.ok) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider refactoring error handling for fetch responses.

Since similar error handling logic is repeated across API methods, extracting a helper function for handling response errors could reduce redundancy and improve maintainability.

Suggested implementation:

async function handleResponse(response: Response): Promise<Response> {
  if (!response.ok) {
    throw new Error(`Failed to fetch: ${response.statusText}`);
  }
  return response;
}


// Add filter parameters
    const rawResponse = await fetch(url);
    const response = await handleResponse(rawResponse);

Depending on your codebase, you may want to reuse handleResponse in other API methods in this file. Make sure to update all similar inline error handling blocks to call handleResponse and adjust variable names accordingly.

}
}

private async _handleExpandItem(event: CustomEvent): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring complex methods into smaller helper functions.

Consider extracting repeated or multi-step logic into helper functions. For example, you can refactor _handleExpandItem by moving the creation and rendering of the details component into its own helper, which would reduce nesting and clarify responsibilities.

Before:

private async _handleExpandItem(event: CustomEvent): Promise<void> {
  const eccUtilsDesignCollection = this.getCollectionElement();
  if (!eccUtilsDesignCollection) return;

  const { target, detail } = event;
  if (!target || !(target instanceof HTMLElement)) {
    console.error("Target is null or not an HTMLElement");
    return;
  }

  const { key } = detail;
  const existingContent = target.querySelector(`[slot='${key}']`);
  if (existingContent) {
    console.log("Content already exists in slot:", key);
    return;
  }
  if (!this.api) return;

  try {
    const toolData = this.tools.find((tool: any) => tool.id === key);
    if (!toolData) {
      console.error(`Failed to find tool with ID: ${key}`);
      return;
    }
    this.cache.set(key, toolData);

    const child = document.createElement("div");
    child.setAttribute("slot", key);
    target.appendChild(child);

    const detailsComponent = document.createElement("ecc-utils-design-details");
    detailsComponent.classList.add("details");
    detailsComponent.id = key;
    detailsComponent.data = toolData;
    detailsComponent.fields = ECCClientGa4ghTrsTools.defaultFields;
    child.appendChild(detailsComponent);

    console.log("Added details component for:", key);
  } catch (error) {
    console.error(`Failed to process data for tool: ${key}`, error);
  }
}

After:

Split the creation of the details component and its container into helper functions.

private createDetailsContainer(key: string): HTMLDivElement {
  const container = document.createElement("div");
  container.setAttribute("slot", key);
  return container;
}

private createDetailsComponent(key: string, toolData: any): HTMLElement {
  const detailsComponent = document.createElement("ecc-utils-design-details");
  detailsComponent.classList.add("details");
  detailsComponent.id = key;
  detailsComponent.data = toolData;
  detailsComponent.fields = ECCClientGa4ghTrsTools.defaultFields;
  return detailsComponent;
}

private async _handleExpandItem(event: CustomEvent): Promise<void> {
  const collection = this.getCollectionElement();
  if (!collection) return;

  const { target, detail } = event;
  if (!target || !(target instanceof HTMLElement)) {
    console.error("Target is null or not an HTMLElement");
    return;
  }
  const { key } = detail;
  if (target.querySelector(`[slot='${key}']`)) {
    console.log("Content already exists in slot:", key);
    return;
  }
  if (!this.api) return;

  try {
    const toolData = this.tools.find((tool: any) => tool.id === key);
    if (!toolData) {
      console.error(`Failed to find tool with ID: ${key}`);
      return;
    }
    this.cache.set(key, toolData);

    const container = this.createDetailsContainer(key);
    target.appendChild(container);

    const detailsComponent = this.createDetailsComponent(key, toolData);
    container.appendChild(detailsComponent);

    console.log("Added details component for:", key);
  } catch (error) {
    console.error(`Failed to process data for tool: ${key}`, error);
  }
}

Similar modularization for complex inline conditional logic in updateFilters can be applied. For instance, separate the search filter configuration and tool class filter creation into helper functions:

private getSearchFilters(): FilterProp[] {
  return [
    {
      key: "search",
      type: "search",
      placeholder: "Search by name",
    },
  ];
}

private getAdditionalFilters(): FilterProp[] {
  return [
    { key: "id", type: "search", placeholder: "Filter by Tool ID" },
    { key: "alias", type: "search", placeholder: "Filter by Alias" },
    { key: "registry", type: "search", placeholder: "Filter by Registry" },
    { key: "organization", type: "search", placeholder: "Filter by Organization" },
    { key: "name", type: "search", placeholder: "Filter by Name" },
    { key: "description", type: "search", placeholder: "Filter by Description" },
    { key: "author", type: "search", placeholder: "Filter by Author" },
  ];
}

private getToolClassFilter(): FilterProp | null {
  if (this.toolClasses.length === 0) return null;
  this.toolClassMap = Object.fromEntries(this.toolClasses.map(tc => [tc.name, tc.id]));
  const toolClassOptions = this.toolClasses.map(tc => tc.name);
  return {
    key: "toolClass",
    type: "select",
    options: toolClassOptions,
    placeholder: "Filter by tool class",
    selectConfig: { multiple: false },
  };
}

private updateFilters(): void {
  let filters: FilterProp[] = [];
  if (this.search) {
    filters = filters.concat(this.getSearchFilters());
    if (this.filter) {
      filters = filters.concat(this.getAdditionalFilters());
    }
  }
  if (this.filter) {
    const toolClassFilter = this.getToolClassFilter();
    if (toolClassFilter) {
      filters.push(toolClassFilter);
    }
  }
  this.filters = filters;
}

These changes keep functionality intact while reducing the complexity of methods, improving readability and maintainability.

@@ -14,7 +14,7 @@
<body>
<div style="display: flex; justify-content: center; flex-direction: column">
<ecc-client-ga4gh-trs
baseUrl="https://trs-filer-test.rahtiapp.fi/ga4gh/trs/v2"
baseUrl="https://dockstore.org/api/api/ga4gh/v2"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Hardcoded base URL found in HTML file.

Consider using an environment variable or configuration file to manage the base URL for different environments (e.g., development, testing, production) to enhance security and flexibility.


protected updated(changedProperties: Map<PropertyKey, unknown>): void {
const eccUtilsDesignCollection = this.getCollectionElement();
if (!eccUtilsDesignCollection) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!eccUtilsDesignCollection) return;
if (!eccUtilsDesignCollection) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

}

private async loadToolClasses(): Promise<void> {
if (!this.api) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!this.api) return;
if (!this.api) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

}

private async loadData(): Promise<void> {
if (!this.api) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!this.api) return;
if (!this.api) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

Comment on lines +395 to +407
if (eccUtilsDesignCollection) {
// Don't set totalItems to enable dynamic pagination
// This will allow the collection to show "..." and enable next page button
// as long as there are items returned

// Update UI based on returned items
if (this.items.length === 0 && this.currentPage > 1) {
// If we get no results and we're not on the first page, go back a page
this.currentPage--;
this.loadData();
return;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Merge nested if conditions (merge-nested-ifs)

Suggested change
if (eccUtilsDesignCollection) {
// Don't set totalItems to enable dynamic pagination
// This will allow the collection to show "..." and enable next page button
// as long as there are items returned
// Update UI based on returned items
if (this.items.length === 0 && this.currentPage > 1) {
// If we get no results and we're not on the first page, go back a page
this.currentPage--;
this.loadData();
return;
}
}
if (eccUtilsDesignCollection && (this.items.length === 0 && this.currentPage > 1)) {
this.currentPage--;
this.loadData();
return;
}


ExplanationReading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two if conditions can be combined using
and is an easy win.


private async _handleExpandItem(event: CustomEvent): Promise<void> {
const eccUtilsDesignCollection = this.getCollectionElement();
if (!eccUtilsDesignCollection) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!eccUtilsDesignCollection) return;
if (!eccUtilsDesignCollection) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

return; // Content already exists, no need to add it again
}

if (!this.api) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!this.api) return;
if (!this.api) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant