Skip to content

Improvement/dependency widgets#38

Merged
emlimlf merged 6 commits intomainfrom
improvement/dependency-widgets
Feb 17, 2025
Merged

Improvement/dependency widgets#38
emlimlf merged 6 commits intomainfrom
improvement/dependency-widgets

Conversation

@emlimlf
Copy link
Copy Markdown
Collaborator

@emlimlf emlimlf commented Feb 13, 2025

In this PR:

  • Added contributors dependency widget using mock data
  • Added organization dependency widget using mock data
  • Break some components from the leaderboard widgets into reusable components
  • Added progress bar component

Future work

Need to wire this to actual backend endpoint when it's ready
Require adjustments on UI particularly on empty states and chart hover interactions (when designs are ready)

Tickets:

INS-66
INS-67

Summary by CodeRabbit

  • New Features
    • Enhanced contributor and organization dashboards with dynamic dependency displays, updated leaderboards, and interactive tables.
    • Introduced metric-specific dropdowns and a new progress bar component with distinct visual states for progress indication.
  • Refactor
    • Streamlined UI components for improved modularity and a smoother user experience, including better handling of loading and error states.
  • Style
    • Refined avatar and progress bar styling for increased visual consistency.
  • Chores
    • Added new API endpoints, structured mock data, and additional time period options to support enhanced metrics filtering.

Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
Signed-off-by: Efren Lim <elim@linuxfoundation.org>
@emlimlf emlimlf requested a review from gaspergrom February 13, 2025 09:53
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 13, 2025

Walkthrough

This pull request introduces extensive updates to the application's contributor and organization dependency features. The changes refactor Vue components to incorporate custom metric dropdowns, modular tables, and enhanced loading/error handling. New computed properties and watchers improve dynamic data processing, while additional API endpoints and mock data facilitate backend integration. Updates to shared type definitions and UI kit styles, including a new progress bar component, further improve the overall modularity and maintainability of the codebase.

Changes

File(s) Change Summary
frontend/app/components/modules/project/components/contributors/{contributor-dependency.vue, contributors-leaderboard.vue, organization-dependency.vue, organizations-leaderboard.vue} Updated main components: refactored templates and scripts, replaced standard dropdowns with lfx-metric-dropdown, integrated new table components, and added conditional rendering with loading spinners, error placeholders, and computed properties.
frontend/app/components/modules/project/components/contributors/fragments/{contributors-table.vue, dependency-display.vue, metric-dropdown.vue, organizations-table.vue} Introduced new Vue components for presenting data: encapsulated table views, dropdown functionality, and dependency displays with responsive design and computed logic.
frontend/app/components/shared/types/{contributors.types.ts, time-periods.ts} Enhanced type definitions by adding optional percentage properties, new dependency interfaces, and a constant (timePeriodsOptions) for selecting time periods.
frontend/app/components/uikit/{avatar-group/avatar-group.scss, index.scss, progress-bar/*} Adjusted avatar margin and outline styles; added a new progress bar component with associated SCSS, story documentation, and type definitions to handle various states.
frontend/server/{api/contributors/{contributor-dependency.get.ts, organization-dependency.get.ts}, mocks/{contributor-dependency.mock.ts, organization-dependency.mock.ts}} Added new API endpoints and corresponding mock data for fetching dependency metrics for both contributors and organizations.
frontend/setup/primevue.ts Reformatted the component include array for improved readability.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Contributor Dependency Component
    participant API as Contributor Dependency API
    participant Toast as Toast Notification
    UI->>API: Request dependency data (useFetch)
    API-->>UI: Return data or error
    alt Error Response
        UI->>Toast: Display error notification
        UI->>UI: Render error placeholder
    else Successful Response
        UI->>UI: Compute topContributors & others
        UI->>UI: Render metric dropdown and contributors table
    end
Loading
sequenceDiagram
    participant OrgUI as Organization Dependency Component
    participant OrgAPI as Organization Dependency API
    participant Toast as Toast Notification
    OrgUI->>OrgAPI: Request organization dependency data (useFetch)
    OrgAPI-->>OrgUI: Return data or error
    alt Error Response
        OrgUI->>Toast: Display error notification
        OrgUI->>OrgUI: Render error placeholder
    else Successful Response
        OrgUI->>OrgUI: Compute topOrganizations, avatars & others
        OrgUI->>OrgUI: Render metric dropdown, organizations table, and dependency display
    end
Loading

Possibly related PRs

  • Improvement/ins 62 active contributors #34: Enhancements to contributor data handling with new UI states and computed properties, similar to updates in contributor-dependency.vue.
  • Improvement/leaderboards #35: Major refactoring of the contributors leaderboard structure, including modular dropdown and table components.
  • Frontend init #1: Modifications to the display and handling of contributor data via new component integrations and computed logic.

Suggested reviewers

  • gaspergrom

Poem

I'm a coding bunny, hopping through the lines,
Skipping past bugs, making everything shine.
New dropdowns and tables, UI bright and neat,
API calls and computed props make it all complete.
With each gentle hop, I celebrate this code feat! 🐇
Crunching numbers and styles with a joyful beat.
Happy coding from a rabbit with a digital treat!

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (17)
frontend/app/components/modules/project/components/contributors/contributor-dependency.vue (6)

9-19: Define an error state or empty state.

You have placeholders for the 'error' scenario (lines 16-17). Consider implementing or importing an actual error/empty state component so that the user receives more direct feedback in the event of API failure or no data.


20-44: Ensure fallback for missing contributors.

When top or other contributors are empty, consider rendering a fallback message or skeleton UI. This helps avoid confusion when there is insufficient data.


83-86: Add defensive checks for computed properties.

When accessing fields like .topContributors or .list, ensure the response always includes these properties. Otherwise, default to an empty array to avoid exceptions if the response shape changes.


87-90: Limit avatar generation.

Truncating avatars to three for the display group is a good UI decision. If you need to show a "remaining count", you might add a small badge or text (e.g., "+2 more") to show how many were omitted.


91-94: Use consistent labeling for time periods.

Lowercasing the time period label is consistent. Just ensure that all references across the UI matches, especially if some design require uppercase.


95-104: Fallback or more proactive error handling.

The toast works great for error feedback. You might also index into the error object for more details, or automatically retry once if the error is transient.

frontend/app/components/uikit/progress-bar/progress-bar.stories.ts (1)

1-25: LGTM! Consider adding more stories for different states.

The story configuration is well-structured with proper documentation tags and controls. The default story provides a good starting point.

Consider adding more stories to showcase different states:

export const Positive = {
  args: {
    value: 75,
    color: 'positive'
  }
};

export const Warning = {
  args: {
    value: 50,
    color: 'warning'
  }
};

export const Negative = {
  args: {
    value: 25,
    color: 'negative'
  }
};
frontend/app/components/modules/project/components/contributors/fragments/metric-dropdown.vue (1)

12-34: Optimize imports and add validation.

The component can be improved by optimizing imports and adding validation for the metric options.

-import { computed } from 'vue';
+import { computed, onMounted } from 'vue';
 import LfxDropdown from '~/components/uikit/dropdown/dropdown.vue';
 import { metricsOptions } from '~/components/shared/types/metrics';

 const metricOptions = metricsOptions;

+// Validate that modelValue exists in metricOptions
+onMounted(() => {
+  if (!metricOptions.find(option => option.value === props.modelValue)) {
+    console.warn(`Invalid metric value: ${props.modelValue}`);
+  }
+});
frontend/server/api/contributors/organization-dependency.get.ts (1)

1-1: Add type safety for mock data imports.

Import types to ensure type safety for the mock data.

+import type { OrganizationDependency } from '~/components/shared/types/contributors.types';
-import { allMetrics, commits } from '~~/server/mocks/organization-dependency.mock';
+import { allMetrics, commits } from '~~/server/mocks/organization-dependency.mock' as { 
+  allMetrics: OrganizationDependency,
+  commits: OrganizationDependency 
+};
frontend/app/components/shared/types/contributors.types.ts (2)

53-56: Add JSDoc documentation for the Dependency interface.

Document the purpose and constraints of the interface fields.

+/**
+ * Represents a dependency metric with count and percentage.
+ * @interface Dependency
+ * @property {number} count - The total count of items in this dependency category
+ * @property {number} percentage - The percentage value (0-100) of items in this category
+ */
 export interface Dependency {
   count: number;
   percentage: number;
 }

58-62: Add JSDoc documentation for ContributorDependency and OrganizationDependency interfaces.

Document the purpose and relationship between these interfaces.

+/**
+ * Represents dependency metrics for contributors.
+ * @interface ContributorDependency
+ * @property {Dependency} topContributors - Metrics for top contributors
+ * @property {Dependency} otherContributors - Metrics for remaining contributors
+ * @property {Contributor[]} list - Detailed list of contributors with their metrics
+ */
 export interface ContributorDependency {
   topContributors: Dependency;
   otherContributors: Dependency;
   list: Contributor[];
 }

+/**
+ * Represents dependency metrics for organizations.
+ * @interface OrganizationDependency
+ * @property {Dependency} topOrganizations - Metrics for top organizations
+ * @property {Dependency} otherOrganizations - Metrics for remaining organizations
+ * @property {Organization[]} list - Detailed list of organizations with their metrics
+ */
 export interface OrganizationDependency {
   topOrganizations: Dependency;
   otherOrganizations: Dependency;
   list: Organization[];
 }

Also applies to: 64-68

frontend/app/components/modules/project/components/contributors/fragments/organizations-table.vue (1)

2-6: Add ARIA roles to table structure.

Enhance accessibility by adding proper ARIA roles to the table structure.

-  <div class="lfx-table">
-    <div class="lfx-table-header">
+  <div class="lfx-table" role="table" aria-label="Organizations contributions">
+    <div class="lfx-table-header" role="row">
-      <div>Organization</div>
-      <div>{{ organizationColumnHeader }}</div>
+      <div role="columnheader">Organization</div>
+      <div role="columnheader">{{ organizationColumnHeader }}</div>
     </div>
frontend/app/components/modules/project/components/contributors/fragments/dependency-display.vue (1)

44-53: Document color threshold logic and remove unnecessary type casting.

The color thresholds seem arbitrary and need documentation. Also, type casting to ProgressBarType is unnecessary when returning string literals that match the type.

Apply this diff to improve the code:

-// This needs clarification on how to handle the colors
+// Color thresholds for dependency percentage:
+// - negative (red): > 80% indicates high dependency risk
+// - warning (yellow): > 60% indicates moderate dependency risk
+// - positive (green): <= 60% indicates healthy dependency distribution
 const dependencyColor = computed<ProgressBarType>(() => {
   if (props.topDependency.percentage > 80) {
-    return 'negative' as ProgressBarType;
+    return 'negative';
   }
   if (props.topDependency.percentage > 60) {
-    return 'warning' as ProgressBarType;
+    return 'warning';
   }
-  return 'positive' as ProgressBarType;
+  return 'positive';
 });
frontend/app/components/modules/project/components/contributors/contributors-leaderboard.vue (2)

17-20: Implement error state UI.

The TODO comment indicates missing error state implementation.

Would you like me to help implement an error state component or create an issue to track this task?


58-67: Improve error message for better user experience.

The error message could be more user-friendly and provide guidance on potential solutions.

Apply this diff to improve the error message:

     showToast(
-      `Error fetching contributor leaderboard: ${error.value?.statusMessage}`,
+      `Unable to load contributors data. Please try refreshing the page or contact support if the issue persists.`,
       ToastTypesEnum.negative,
       undefined,
       10000
     );
frontend/app/components/modules/project/components/contributors/organizations-leaderboard.vue (1)

16-19: Implement error state handling.

The error state is currently incomplete with a TODO comment. Consider implementing proper error handling UI to improve user experience.

Would you like me to help implement the error state component or create an issue to track this task?

frontend/app/components/uikit/progress-bar/progress-bar.scss (1)

1-6: Consider accessibility and responsive design improvements.

While the base structure is well-implemented, consider these enhancements:

  1. Add CSS custom properties for configurable dimensions
  2. Enforce ARIA role and attributes through class selectors
 .c-progress-bar {
-  @apply flex flex-row items-stretch justify-between gap-0.5 h-2 w-full;
+  --progress-height: 0.5rem;
+  @apply flex flex-row items-stretch justify-between gap-0.5 w-full;
+  height: var(--progress-height);
+  &[role="progressbar"] {
+    @apply relative;
+  }

   .c-progress-bar__value, .c-progress-bar__empty {
     @apply rounded-sm transition-all;
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4104d41 and fa2dc3c.

📒 Files selected for processing (21)
  • frontend/app/components/modules/project/components/contributors/contributor-dependency.vue (1 hunks)
  • frontend/app/components/modules/project/components/contributors/contributors-leaderboard.vue (3 hunks)
  • frontend/app/components/modules/project/components/contributors/fragments/contributors-table.vue (1 hunks)
  • frontend/app/components/modules/project/components/contributors/fragments/dependency-display.vue (1 hunks)
  • frontend/app/components/modules/project/components/contributors/fragments/metric-dropdown.vue (1 hunks)
  • frontend/app/components/modules/project/components/contributors/fragments/organizations-table.vue (1 hunks)
  • frontend/app/components/modules/project/components/contributors/organization-dependency.vue (1 hunks)
  • frontend/app/components/modules/project/components/contributors/organizations-leaderboard.vue (3 hunks)
  • frontend/app/components/shared/types/contributors.types.ts (2 hunks)
  • frontend/app/components/shared/types/time-periods.ts (1 hunks)
  • frontend/app/components/uikit/avatar-group/avatar-group.scss (1 hunks)
  • frontend/app/components/uikit/index.scss (1 hunks)
  • frontend/app/components/uikit/progress-bar/progress-bar.scss (1 hunks)
  • frontend/app/components/uikit/progress-bar/progress-bar.stories.ts (1 hunks)
  • frontend/app/components/uikit/progress-bar/progress-bar.vue (1 hunks)
  • frontend/app/components/uikit/progress-bar/types/progress-bar.types.ts (1 hunks)
  • frontend/server/api/contributors/contributor-dependency.get.ts (1 hunks)
  • frontend/server/api/contributors/organization-dependency.get.ts (1 hunks)
  • frontend/server/mocks/contributor-dependency.mock.ts (1 hunks)
  • frontend/server/mocks/organization-dependency.mock.ts (1 hunks)
  • frontend/setup/primevue.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/setup/primevue.ts
🔇 Additional comments (16)
frontend/app/components/modules/project/components/contributors/contributor-dependency.vue (4)

3-6: Clearer heading and description look good.

The updated heading and descriptive text for Contributor dependency provide helpful context. This improves the user’s understanding of the data shown below.


51-66: Validate route parameters.

Using useRoute is fine, but be sure that slug and name are reliably present. Handle cases where they may be undefined, or provide fallback logic within the fetch call.


77-82: Check for potential request caching.

useFetch could cache responses if configured. If dynamic updates are needed (for example, changing the metric mid-session), confirm that you’re invalidating or refreshing the cache as intended.


109-110: Naming aligns with existing ecosystem.

Naming the component LfxProjectContributorDependency is consistent with other Lfx components. This fosters a cohesive naming scheme across the codebase.

frontend/app/components/uikit/progress-bar/types/progress-bar.types.ts (1)

1-3: Good approach with string literal union.

Using as const and a corresponding type provides strong typing and prevents invalid string assignments. This keeps your progress bar color variants well-scoped.

frontend/app/components/shared/types/time-periods.ts (1)

1-11: Versatile time periods for thorough coverage.

Defining multiple time ranges (e.g., 90d, custom, etc.) covers common use cases. It’s a handy structure, especially if you plan to introduce dynamic filtering or i18n labels later.

frontend/server/api/contributors/contributor-dependency.get.ts (1)

3-24: LGTM! Well-documented data format.

The documentation clearly describes the expected data format and structure.

frontend/app/components/modules/project/components/contributors/fragments/dependency-display.vue (1)

1-24: LGTM! Well-structured and responsive layout.

The template has a clean and maintainable structure with good use of semantic HTML and flexbox for responsive design.

frontend/app/components/modules/project/components/contributors/organizations-leaderboard.vue (1)

10-10: LGTM! Good component refactoring.

The refactoring to use dedicated LfxMetricDropdown and LfxOrganizationsTable components improves code modularity and maintainability.

Also applies to: 20-24

frontend/app/components/modules/project/components/contributors/organization-dependency.vue (3)

15-18: Implement error state handling.

Similar to the organizations leaderboard, the error state needs to be implemented.


22-34: LGTM! Well-structured dependency visualization.

The implementation effectively uses LfxDependencyDisplay with avatar groups to visualize organization dependencies. The component structure is clean and modular.


86-98: LGTM! Clean data transformation.

Good use of computed properties for transforming and filtering data. The implementation is efficient and maintainable.

frontend/app/components/uikit/avatar-group/avatar-group.scss (1)

4-14: LGTM! Improved avatar group styling.

The adjustments to margins and outline provide better visual separation and hierarchy. The changes are consistent across different avatar sizes.

frontend/app/components/uikit/index.scss (1)

18-18: LGTM! Added progress bar styles.

Clean addition of progress bar styles following the established pattern.

frontend/app/components/uikit/progress-bar/progress-bar.scss (2)

29-31: LGTM!

The empty state styling is clean and effective, using flex grow for dynamic sizing and a neutral color that won't compete with the value indicator.


8-27:

Details

❓ Verification inconclusive

Verify color contrast ratios and enhance visual feedback.

The state modifiers use semantic colors, but consider:

  1. Verifying color contrast ratios for accessibility
  2. Adding hover/focus states if the progress bar is interactive

🌐 Web query:

What are the WCAG 2.1 color contrast requirements for progress indicators?

💡 Result:

WCAG 2.1 color contrast requirements for progress indicators primarily fall under Success Criterion 1.4.11 Non-text Contrast (Level AA)[1][6][8]. Here’s a breakdown:

Non-Text Elements

  • Progress bars/indicators (e.g., graphical segments or icons) must have a minimum contrast ratio of 3:1 against adjacent colors[1][6][8].
  • Example: A loading bar’s filled vs. unfilled sections must meet this ratio to ensure visibility[8].

Text Elements

  • Text labels within progress indicators (e.g., “Step 2 of 5”) must follow 1.4.3 Contrast (Minimum):
    • Normal text: ≥4.5:1 contrast[1][9].
    • Large text (18pt+ or 14pt bold+): ≥3:1[1][9].

Additional Requirements

  • State changes (e.g., completed steps marked with checkmarks) must maintain sufficient contrast (3:1) for their visual indicators[6].
  • Color reliance: Avoid using color alone to convey progress status (e.g., pair color with text labels or patterns) to comply with 1.4.1 Use of Color[4][6].

Key Exceptions

  • Decorative elements without functional meaning are exempt[5].
  • Inactive components (e.g., steps not yet available) have no contrast requirement[6].

For compliance, use tools like WebAIM’s Contrast Checker[7] to verify ratios between foreground and background colors in all states of your progress indicator design[4].

Citations:


Action Required: Enhance Visual Feedback & Verify Contrast

The state modifiers use semantic Tailwind colors effectively. However, please consider the following improvements:

  • Contrast Verification: Ensure that the colors applied to the progress bar (filled vs. unfilled or adjacent areas) meet the WCAG 2.1 Non-Text Contrast requirement of at least a 3:1 ratio.
  • Interactive States: If the progress bar is intended to be interactive, add hover and focus state styles to provide clear visual feedback for mouse and keyboard interactions.

Comment on lines +1 to +7
<template>
<!-- This component might change based on Nuno's feedback -->
<div :class="`c-progress-bar c-progress-bar--${props.color}`">
<div class="c-progress-bar__value" :style="{ width: `${props.value}%` }" />
<div class="c-progress-bar__empty" />
</div>
</template>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add ARIA attributes for accessibility.

The progress bar should include ARIA attributes to make it accessible to screen readers.

-  <div :class="`c-progress-bar c-progress-bar--${props.color}`">
+  <div
+    :class="`c-progress-bar c-progress-bar--${props.color}`"
+    role="progressbar"
+    :aria-valuenow="props.value"
+    aria-valuemin="0"
+    aria-valuemax="100">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<template>
<!-- This component might change based on Nuno's feedback -->
<div :class="`c-progress-bar c-progress-bar--${props.color}`">
<div class="c-progress-bar__value" :style="{ width: `${props.value}%` }" />
<div class="c-progress-bar__empty" />
</div>
</template>
<template>
<!-- This component might change based on Nuno's feedback -->
<div
:class="`c-progress-bar c-progress-bar--${props.color}`"
role="progressbar"
:aria-valuenow="props.value"
aria-valuemin="0"
aria-valuemax="100">
<div class="c-progress-bar__value" :style="{ width: `${props.value}%` }" />
<div class="c-progress-bar__empty" />
</div>
</template>

Comment on lines +9 to +22
<script setup lang="ts">
import type { ProgressBarType } from './types/progress-bar.types';

const props = withDefaults(
defineProps<{
value: number;
// TODO: change this once we have the correct types
color?: ProgressBarType;
}>(),
{
color: 'normal'
}
);
</script>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add prop validation and address the TODO comment.

The value prop should include validation to ensure it's between 0 and 100. Also, the TODO comment about types should be addressed.

 const props = withDefaults(
   defineProps<{
-    value: number;
-    // TODO: change this once we have the correct types
+    value: number & { __type: 'between 0 and 100' };
     color?: ProgressBarType;
   }>(),
   {
     color: 'normal'
   }
 );

+// Runtime validation for value prop
+watch(() => props.value, (newValue) => {
+  if (newValue < 0 || newValue > 100) {
+    console.warn('Progress bar value must be between 0 and 100');
+  }
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<script setup lang="ts">
import type { ProgressBarType } from './types/progress-bar.types';
const props = withDefaults(
defineProps<{
value: number;
// TODO: change this once we have the correct types
color?: ProgressBarType;
}>(),
{
color: 'normal'
}
);
</script>
<script setup lang="ts">
import type { ProgressBarType } from './types/progress-bar.types';
const props = withDefaults(
defineProps<{
value: number & { __type: 'between 0 and 100' };
color?: ProgressBarType;
}>(),
{
color: 'normal'
}
);
// Runtime validation for value prop
watch(() => props.value, (newValue) => {
if (newValue < 0 || newValue > 100) {
console.warn('Progress bar value must be between 0 and 100');
}
});
</script>

Comment on lines +1 to +10
<template>
<div class="flex flex-row gap-4 items-center mb-6">
<lfx-dropdown
v-model="metric"
icon="fa-light fa-display-code"
:options="metricOptions"
full-width
center />
</div>
</template>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add accessibility attributes to the dropdown.

The dropdown should include ARIA attributes and a label for screen readers.

-  <div class="flex flex-row gap-4 items-center mb-6">
+  <div class="flex flex-row gap-4 items-center mb-6" role="group" aria-label="Metric Selection">
     <lfx-dropdown
       v-model="metric"
       icon="fa-light fa-display-code"
       :options="metricOptions"
+      aria-label="Select metric"
       full-width
       center />
   </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<template>
<div class="flex flex-row gap-4 items-center mb-6">
<lfx-dropdown
v-model="metric"
icon="fa-light fa-display-code"
:options="metricOptions"
full-width
center />
</div>
</template>
<template>
<div class="flex flex-row gap-4 items-center mb-6" role="group" aria-label="Metric Selection">
<lfx-dropdown
v-model="metric"
icon="fa-light fa-display-code"
:options="metricOptions"
aria-label="Select metric"
full-width
center />
</div>
</template>

Comment on lines +32 to +41
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const { metric } = query;

if (metric === 'all') {
return allMetrics;
}

return commits;
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling and query parameter validation.

The endpoint should validate query parameters and handle potential errors.

 export default defineEventHandler(async (event) => {
   const query = getQuery(event);
-  const { metric } = query;
+  const { metric, project, repository, 'time-period': timePeriod } = query;
+
+  // Validate required parameters
+  if (!metric || !project) {
+    throw createError({
+      statusCode: 400,
+      message: 'Missing required parameters: metric and project'
+    });
+  }
+
+  // Validate metric value
+  const validMetrics = ['all', 'commits']; // Add other valid metrics
+  if (!validMetrics.includes(metric as string)) {
+    throw createError({
+      statusCode: 400,
+      message: `Invalid metric. Must be one of: ${validMetrics.join(', ')}`
+    });
+  }

   if (metric === 'all') {
     return allMetrics;
   }

   return commits;
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const { metric } = query;
if (metric === 'all') {
return allMetrics;
}
return commits;
});
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const { metric, project, repository, 'time-period': timePeriod } = query;
// Validate required parameters
if (!metric || !project) {
throw createError({
statusCode: 400,
message: 'Missing required parameters: metric and project'
});
}
// Validate metric value
const validMetrics = ['all', 'commits']; // Add other valid metrics if needed
if (!validMetrics.includes(metric as string)) {
throw createError({
statusCode: 400,
message: `Invalid metric. Must be one of: ${validMetrics.join(', ')}`
});
}
if (metric === 'all') {
return allMetrics;
}
return commits;
});

Comment on lines +32 to +41
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const { metric } = query;

if (metric === 'all') {
return allMetrics;
}

return commits;
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling and input validation.

The endpoint needs additional error handling and validation:

  1. Validate required query parameters (project, repository, time-period)
  2. Handle invalid metric values
  3. Add type safety for the response data

Consider this implementation:

 export default defineEventHandler(async (event) => {
   const query = getQuery(event);
-  const { metric } = query;
+  const { metric, project, repository, timePeriod } = query;
+
+  // Validate required parameters
+  if (!project || !repository || !timePeriod) {
+    throw createError({
+      statusCode: 400,
+      message: 'Missing required query parameters: project, repository, timePeriod'
+    });
+  }
+
+  // Validate metric value
+  const validMetrics = ['all', 'commits'];
+  if (metric && !validMetrics.includes(metric as string)) {
+    throw createError({
+      statusCode: 400,
+      message: `Invalid metric. Must be one of: ${validMetrics.join(', ')}`
+    });
+  }

   if (metric === 'all') {
     return allMetrics;
   }

   return commits;
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const { metric } = query;
if (metric === 'all') {
return allMetrics;
}
return commits;
});
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const { metric, project, repository, timePeriod } = query;
// Validate required parameters
if (!project || !repository || !timePeriod) {
throw createError({
statusCode: 400,
message: 'Missing required query parameters: project, repository, timePeriod'
});
}
// Validate metric value
const validMetrics = ['all', 'commits'];
if (metric && !validMetrics.includes(metric as string)) {
throw createError({
statusCode: 400,
message: `Invalid metric. Must be one of: ${validMetrics.join(', ')}`
});
}
if (metric === 'all') {
return allMetrics;
}
return commits;
});

Comment on lines +8 to +14
<div v-for="(organization, index) in props.organizations" :key="index" class="lfx-table-row">
<div class="flex flex-row gap-3 items-center">
<lfx-avatar :src="organization.logo" type="organization" />
<div>{{ organization.name }}</div>
</div>
<div>{{ formatNumber(organization.contributions) }}</div>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve accessibility and list rendering.

  1. Add ARIA labels for better accessibility
  2. Use unique identifier instead of index as key
  3. Add empty state handling
-    <div v-for="(organization, index) in props.organizations" :key="index" class="lfx-table-row">
+    <div 
+      v-for="organization in props.organizations" 
+      :key="organization.name" 
+      class="lfx-table-row"
+      role="row"
+      :aria-label="`Organization ${organization.name} with ${organization.contributions} contributions`"
+    >
       <div class="flex flex-row gap-3 items-center">
-        <lfx-avatar :src="organization.logo" type="organization" />
+        <lfx-avatar :src="organization.logo" type="organization" :alt="`${organization.name} logo`" />
         <div>{{ organization.name }}</div>
       </div>
       <div>{{ formatNumber(organization.contributions) }}</div>
     </div>
+    <div v-if="!props.organizations.length" class="p-4 text-center text-gray-500">
+      No organizations found
+    </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div v-for="(organization, index) in props.organizations" :key="index" class="lfx-table-row">
<div class="flex flex-row gap-3 items-center">
<lfx-avatar :src="organization.logo" type="organization" />
<div>{{ organization.name }}</div>
</div>
<div>{{ formatNumber(organization.contributions) }}</div>
</div>
<div
v-for="organization in props.organizations"
:key="organization.name"
class="lfx-table-row"
role="row"
:aria-label="`Organization ${organization.name} with ${organization.contributions} contributions`"
>
<div class="flex flex-row gap-3 items-center">
<lfx-avatar :src="organization.logo" type="organization" :alt="`${organization.name} logo`" />
<div>{{ organization.name }}</div>
</div>
<div>{{ formatNumber(organization.contributions) }}</div>
</div>
<div v-if="!props.organizations.length" class="p-4 text-center text-gray-500">
No organizations found
</div>

Comment on lines +1 to +19
<template>
<div class="lfx-table">
<div class="lfx-table-header">
<div>Contributor</div>
<div>{{ contributionColumnHeader }}</div>
</div>

<div v-for="(contributor, index) in props.contributors" :key="index" class="lfx-table-row">
<div class="flex flex-row gap-3 items-center">
<lfx-avatar :src="contributor.avatar" type="member" />
<div>{{ contributor.name }}</div>
</div>
<div>
{{ formatNumber(contributor.contributions) }}
<span v-if="props.showPercentage"> ({{ contributor.percentage }}%) </span>
</div>
</div>
</div>
</template>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider creating a base table component.

There's significant code duplication between this component and organizations-table.vue. Consider extracting common functionality into a base table component.

Create a new base component BaseTable.vue:

<template>
  <div class="lfx-table" role="table" :aria-label="tableLabel">
    <div class="lfx-table-header" role="row">
      <div role="columnheader">{{ entityLabel }}</div>
      <div role="columnheader">{{ columnHeader }}</div>
    </div>

    <div 
      v-for="item in items" 
      :key="item.name" 
      class="lfx-table-row"
      role="row"
      :aria-label="`${entityLabel} ${item.name} with ${item.contributions} contributions`"
    >
      <div class="flex flex-row gap-3 items-center">
        <lfx-avatar :src="item.avatar || item.logo" :type="avatarType" :alt="`${item.name} ${avatarType === 'member' ? 'avatar' : 'logo'}`" />
        <div>{{ item.name }}</div>
      </div>
      <div>
        {{ formatNumber(item.contributions) }}
        <slot name="extra-info" :item="item"></slot>
      </div>
    </div>
    <div v-if="!items.length" class="p-4 text-center text-gray-500">
      No {{ entityLabel.toLowerCase() }}s found
    </div>
  </div>
</template>

Then update this component to use the base:

 <template>
-  <div class="lfx-table">
-    <!-- ... existing template ... -->
-  </div>
+  <base-table
+    entity-label="Contributor"
+    :column-header="contributionColumnHeader"
+    :items="contributors"
+    avatar-type="member"
+    table-label="Contributors contributions"
+  >
+    <template #extra-info="{ item }">
+      <span v-if="showPercentage"> ({{ item.percentage }}%) </span>
+    </template>
+  </base-table>
 </template>

Comment on lines +1 to +47
export const allMetrics = {
topContributors: {
count: 3,
percentage: 68
},
otherContributors: {
count: 1000,
percentage: 32
},
list: [
{
avatar: 'https://i.pravatar.cc/150?u=john.doe@example.com',
name: 'John Doe',
contributions: 1000,
percentage: 20,
email: 'john.doe@example.com'
},
{
avatar: 'https://i.pravatar.cc/150?u=jane.doe@example.com',
name: 'Jane Doe',
contributions: 900,
percentage: 15,
email: 'jane.doe@example.com'
},
{
avatar: 'https://i.pravatar.cc/150?u=john.smith@example.com',
name: 'John Smith',
contributions: 800,
percentage: 10,
email: 'john.smith@example.com'
},
{
avatar: 'https://i.pravatar.cc/150?u=jane.smith@example.com',
name: 'Jane Smith',
contributions: 700,
percentage: 8,
email: 'jane.smith@example.com'
},
{
avatar: 'https://i.pravatar.cc/150?u=jim.smith@example.com',
name: 'Tom Ford',
contributions: 600,
percentage: 5,
email: 'tom.ford@example.com'
}
]
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix data consistency in mock percentages.

The individual contributor percentages (20% + 15% + 10% + 8% + 5% = 58%) don't sum up to the topContributors.percentage (68%).

Also, using an external service (pravatar.cc) for mock avatars could make tests unreliable.

Consider using local placeholder images or base64 encoded images for more reliable tests.

Comment on lines +1 to +85
export const allMetrics = {
topOrganizations: {
count: 2,
percentage: 82
},
otherOrganizations: {
count: 100,
percentage: 18
},
list: [
{
logo: 'https://banner2.cleanpng.com/20240110/flp/transparent-google-logo-colorful-google-logo-on-black-1710930944198.webp',
name: 'Google',
contributions: 10000,
website: 'https://www.google.com'
},
{
logo: 'https://e7.pngegg.com/pngimages/782/185/png-clipart-desktop-apple-logo-apple-logo-computer-logo.png',
name: 'Apple',
contributions: 9000,
website: 'https://www.apple.com'
},
{
logo: 'https://cdn-icons-png.flaticon.com/256/732/732221.png',
name: 'Microsoft',
contributions: 8000,
website: 'https://www.microsoft.com'
},
{
logo: 'https://i.pinimg.com/originals/01/ca/da/01cada77a0a7d326d85b7969fe26a728.jpg',
name: 'Amazon',
contributions: 7000,
website: 'https://www.amazon.com'
},
{
logo: 'https://i.pinimg.com/474x/f7/99/20/f79920f4cb34986684e29df42ec0cebe.jpg',
name: 'Facebook',
contributions: 6000,
website: 'https://www.facebook.com'
}
]
};

export const commits = {
topOrganizations: {
count: 3,
percentage: 73
},
otherOrganizations: {
count: 99,
percentage: 27
},
list: [
{
logo: 'https://e7.pngegg.com/pngimages/708/311/png-clipart-twitter-twitter-thumbnail.png',
name: 'Twitter',
contributions: 5000,
website: 'https://www.twitter.com'
},
{
logo: 'https://www.citypng.com/public/uploads/preview/square-instagram-logo-photos-social-media-701751694793082uj6ej5fkfx.png',
name: 'Instagram',
contributions: 4000,
website: 'https://www.instagram.com'
},
{
logo: 'https://e7.pngegg.com/pngimages/1022/248/png-clipart-tesla-logo-car-logo-tesla-icons-logos-emojis-car-logos-thumbnail.png',
name: 'Tesla',
contributions: 3000,
website: 'https://www.tesla.com'
},
{
logo: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSFf8io--ELan4yZyqWVpDWOckW2IzHD8Cfwg&s',
name: 'Netflix',
contributions: 2000,
website: 'https://www.netflix.com'
},
{
logo: 'https://i.pinimg.com/originals/e9/da/0c/e9da0c83b0a7ec866e17c100079c9d88.jpg',
name: 'WhatsApp',
contributions: 1000,
website: 'https://www.whatsapp.com'
}
]
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace external image URLs with local assets.

Using external image URLs (especially Google-cached ones) in mock data could make tests unreliable as the URLs might expire or be blocked.

Consider:

  1. Using local placeholder images in the project's assets directory
  2. Using base64 encoded images for mock data
  3. Creating a dedicated mock assets directory for test data

@emlimlf emlimlf merged commit 83a6518 into main Feb 17, 2025
1 check passed
@emlimlf emlimlf deleted the improvement/dependency-widgets branch February 17, 2025 06:06
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.

2 participants