Skip to content

Commit 8285184

Browse files
authored
feat(dashboards): add metrics api integration and metric card component (#186)
* feat(dashboards): add metrics api integration and metric card component LFXV2-834 - Add health metrics daily, unique contributors daily, and events monthly API endpoints - Create reusable MetricCardComponent for unified dashboard metric display - Refactor foundation-health, recent-progress, and organization-involvement to use MetricCard - Add DashboardMetricCard interface for type-safe metric definitions - Implement chart tooltip callbacks for enhanced data visualization - Consolidate progress metrics constants and analytics data interfaces Signed-off-by: Asitha de Silva <[email protected]> * refactor(shared): consolidate dashboard constants and interfaces - Consolidate foundation-health, organization-involvement, and progress-metrics constants into single dashboard-metrics.constants.ts file - Move EMPTY_CHART_DATA and NO_TOOLTIP_CHART_OPTIONS to chart-options.constants.ts - Merge dashboard.interface.ts and foundation-metrics.interface.ts into dashboard-metric.interface.ts - Remove sparkline customContentType and use default metric card - Simplify organization-involvement component by using shared imports Signed-off-by: Asitha de Silva <[email protected]> * feat(dashboards): add organization involvement metrics api integration - Add individual API endpoints for organization metrics: - /certified-employees - certification data by foundation - /membership-tier - membership tier data by foundation - /organization-maintainers - maintainer counts - /organization-contributors - contributor counts - /training-enrollments - enrollment data with cumulative charts - Add TypeScript interfaces for Snowflake data mapping - Update organization-involvement component to use live API data - Add dummy chart data for metrics without live chart data yet - Training enrollments shows cumulative chart with formatted dates LFXV2-834 Signed-off-by: Asitha de Silva <[email protected]> --------- Signed-off-by: Asitha de Silva <[email protected]>
1 parent 63e8717 commit 8285184

26 files changed

+2939
-1849
lines changed

apps/lfx-one/src/app/modules/dashboards/components/dashboard-meeting-card/dashboard-meeting-card.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class DashboardMeetingCardComponent {
5757
label: config.label,
5858
className: `${config.bgColor} ${config.textColor}`,
5959
severity,
60-
icon: config.icon,
60+
icon: `${config.icon} mr-2`,
6161
};
6262
});
6363

apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html

Lines changed: 68 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -62,76 +62,85 @@ <h2>{{ title() }}</h2>
6262
<div class="overflow-hidden">
6363
<div #carouselScroll class="flex gap-4 overflow-x-auto pb-2 hide-scrollbar scroll-smooth" data-testid="foundation-health-carousel">
6464
@for (card of metricCards(); track card.title) {
65-
<div class="p-4 bg-white rounded-lg border border-slate-200 flex-shrink-0 w-[calc(100vw-3rem)] md:w-80" [attr.data-testid]="card.testId">
66-
<div class="flex flex-col h-full justify-between">
67-
<!-- Card Header -->
68-
<div class="flex items-center gap-2">
69-
<i [class]="card.icon + ' w-4 h-4 text-muted-foreground'"></i>
70-
<h5 class="text-sm font-medium">{{ card.title }}</h5>
71-
</div>
65+
@switch (card.customContentType) {
66+
<!-- Bar Chart - Use shared metric card -->
67+
@case ('bar-chart') {
68+
<lfx-metric-card
69+
[title]="card.title"
70+
[icon]="card.icon"
71+
[testId]="card.testId"
72+
[chartType]="card.chartType"
73+
[chartData]="card.chartData"
74+
[chartOptions]="barChartOptions"
75+
[value]="card.value"
76+
[subtitle]="card.subtitle"></lfx-metric-card>
77+
}
7278

73-
<!-- Custom Content -->
74-
@switch (card.customContentType) {
75-
<!-- Sparkline Chart -->
76-
@case ('sparkline') {
77-
@if (card.chartData) {
78-
<div class="mt-3 w-full h-16">
79-
<lfx-chart [type]="'line'" [data]="card.chartData" [options]="card.chartOptions || sparklineOptions" height="100%"></lfx-chart>
80-
</div>
81-
}
82-
}
83-
84-
<!-- Bar Chart -->
85-
@case ('bar-chart') {
86-
@if (card.chartData) {
87-
<div class="mt-3 flex justify-center">
88-
<div class="w-[200px] h-[60px]">
89-
<lfx-chart [type]="'bar'" [data]="card.chartData" [options]="barChartOptions" height="100%"></lfx-chart>
90-
</div>
91-
</div>
92-
}
93-
}
94-
95-
<!-- Top Projects List -->
96-
@case ('top-projects') {
79+
<!-- Top Projects List - Custom content -->
80+
@case ('top-projects') {
81+
<lfx-metric-card
82+
[title]="card.title"
83+
[icon]="card.icon"
84+
[testId]="card.testId"
85+
[chartType]="card.chartType"
86+
[value]="card.value"
87+
[subtitle]="card.subtitle">
88+
<ng-template #customContent>
9789
@if (card.topProjects) {
9890
<div class="space-y-0.5 p-0 pl-[60px] m-0 mb-[5px]">
99-
<div class="text-xs font-medium text-muted-foreground">Top 3 Projects by Value</div>
91+
<div class="text-xs font-medium text-gray-500">Top 3 Projects by Value</div>
10092
@for (project of card.topProjects; track project.name) {
10193
<div class="flex items-center justify-between text-sm">
102-
<span class="text-muted-foreground">{{ project.name }}</span>
94+
<span class="text-gray-500">{{ project.name }}</span>
10395
<span class="font-medium">{{ project.formattedValue }}</span>
10496
</div>
10597
}
10698
</div>
10799
}
108-
}
100+
</ng-template>
101+
</lfx-metric-card>
102+
}
109103

110-
<!-- Company Bus Factor (Inline) -->
111-
@case ('bus-factor') {
104+
<!-- Company Bus Factor - Custom content -->
105+
@case ('bus-factor') {
106+
<lfx-metric-card
107+
[title]="card.title"
108+
[icon]="card.icon"
109+
[testId]="card.testId"
110+
[chartType]="card.chartType"
111+
[value]="card.value"
112+
[subtitle]="card.subtitle">
113+
<ng-template #customContent>
112114
@if (card.busFactor) {
113115
<div class="flex-1 flex flex-col justify-end pb-2">
114116
<div class="flex gap-0">
115-
<!-- Top Companies Section -->
116117
<div [style.width.%]="card.busFactor.topCompaniesPercentage" class="flex flex-col gap-1">
117118
<div class="text-xs font-medium text-slate-700">
118119
{{ card.busFactor.topCompaniesCount }} Companies ({{ card.busFactor.topCompaniesPercentage }}%)
119120
</div>
120121
<div class="h-4 rounded-l-sm bg-gray-300"></div>
121122
</div>
122-
123-
<!-- Other Companies Section -->
124123
<div [style.width.%]="card.busFactor.otherCompaniesPercentage" class="flex flex-col gap-1">
125124
<div class="text-xs text-slate-600 text-right">{{ card.busFactor.otherCompaniesCount }} Other Companies</div>
126125
<div class="h-4 bg-slate-200 rounded-r-sm"></div>
127126
</div>
128127
</div>
129128
</div>
130129
}
131-
}
130+
</ng-template>
131+
</lfx-metric-card>
132+
}
132133

133-
<!-- Project Health Scores -->
134-
@case ('health-scores') {
134+
<!-- Project Health Scores - Custom content -->
135+
@case ('health-scores') {
136+
<lfx-metric-card
137+
[title]="card.title"
138+
[icon]="card.icon"
139+
[testId]="card.testId"
140+
[chartType]="card.chartType"
141+
[value]="card.value"
142+
[subtitle]="card.subtitle">
143+
<ng-template #customContent>
135144
@if (card.healthScores) {
136145
<div class="flex-1 flex flex-col justify-end p-0">
137146
<div class="flex items-end justify-between gap-2 h-16">
@@ -143,29 +152,30 @@ <h5 class="text-sm font-medium">{{ card.title }}</h5>
143152
[style.height.px]="item.heightPx"></div>
144153
<div class="text-[10px] text-center whitespace-nowrap">
145154
<div class="font-medium">{{ item.category }}</div>
146-
<div class="text-muted-foreground">{{ item.count }}</div>
155+
<div class="text-gray-500">{{ item.count }}</div>
147156
</div>
148157
</div>
149158
}
150159
</div>
151160
</div>
152161
}
153-
}
154-
}
162+
</ng-template>
163+
</lfx-metric-card>
164+
}
155165

156-
<!-- Card Footer (Value and Subtitle) -->
157-
@if (card.value || card.subtitle) {
158-
<div class="space-y-1">
159-
@if (card.value) {
160-
<div class="text-xl font-medium">{{ card.value }}</div>
161-
}
162-
@if (card.subtitle) {
163-
<div class="text-xs text-gray-500">{{ card.subtitle }}</div>
164-
}
165-
</div>
166-
}
167-
</div>
168-
</div>
166+
<!-- Default fallback -->
167+
@default {
168+
<lfx-metric-card
169+
[title]="card.title"
170+
[icon]="card.icon"
171+
[testId]="card.testId"
172+
[chartType]="card.chartType"
173+
[chartData]="card.chartData"
174+
[chartOptions]="card.chartOptions"
175+
[value]="card.value"
176+
[subtitle]="card.subtitle"></lfx-metric-card>
177+
}
178+
}
169179
}
170180
</div>
171181
</div>

0 commit comments

Comments
 (0)