Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,116 @@
<h3 class="text-heading-3 font-semibold font-secondary pb-3">
Forks
</h3>
<p class="text-body-2 text-neutral-500">
<p class="text-body-2 text-neutral-500 mb-6">
Active contributor is an individual performing tasks such as commits,
issues, or pull requests during the selected time period.
</p>
<hr>
<section class="mt-5">
<div v-if="status === 'success'" class="flex flex-row gap-4 items-center mb-6">
<div class="text-data-display-1">{{ formatNumber(summary.current) }}</div>
<lfx-delta-display :summary="summary" icon="circle-arrow-up-right" icon-type="solid" />
</div>

<lfx-tabs :tabs="tabs" :model-value="activeTab" @update:model-value="activeTab = $event" />
<div class="w-full h-[330px]">
<lfx-chart v-if="status !== 'pending'" :config="lineChartConfig" />
<lfx-spinner v-else />
</div>
</section>
</lfx-card>
</template>

<script setup lang="ts">
import LfxCard from "~/components/uikit/card/card.vue";
import { useFetch, useRoute } from 'nuxt/app';
import { ref, computed, watch } from 'vue';
import type { ForksData } from './types/popularity.types';
import type { Summary } from '~/components/shared/types/summary.types';
import LfxCard from '~/components/uikit/card/card.vue';
import LfxDeltaDisplay from '~/components/uikit/delta-display/delta-display.vue';
import LfxTabs from '~/components/uikit/tabs/tabs.vue';
import { convertToChartData } from '~/components/uikit/chart/helpers/chart-helpers';
import type {
ChartData,
RawChartData,
ChartSeries
} from '~/components/uikit/chart/types/ChartTypes';
import LfxChart from '~/components/uikit/chart/chart.vue';
import { getBarChartConfig } from '~/components/uikit/chart/configs/bar.chart';
import { lfxColors } from '~/config/styles/colors';
import { axisLabelFormatter } from '~/components/uikit/chart/helpers/formatters';
import useToastService from '~/components/uikit/toast/toast.service';
import { ToastTypesEnum } from '~/components/uikit/toast/types/toast.types';
import LfxSpinner from '~/components/uikit/spinner/spinner.vue';
import { formatNumber } from '~/components/shared/utils/formatter';

const props = withDefaults(
defineProps<{
timePeriod?: string;
}>(),
{
timePeriod: '90d'
}
);

const { showToast } = useToastService();

const activeTab = ref('cumulative');
const route = useRoute();

const { data, status, error } = useFetch(
() => `/api/projects/popularity/forks?type=${activeTab.value}&project=${route.params.slug
}&repository=${route.params.name || ''}&time-period=${props.timePeriod}`
);

const forks = computed<ForksData>(() => data.value as ForksData);

const summary = computed<Summary>(() => forks.value.summary);
const chartData = computed<ChartData[]>(
// convert the data to chart data
() => convertToChartData(forks.value.data as RawChartData[], 'dateFrom', [
'forks'
])
);

const tabs = [
{ label: 'Cumulative', value: 'cumulative' },
{ label: 'New', value: 'new' }
];

const chartSeries = ref<ChartSeries[]>([
{
name: 'Forks',
type: 'bar',
yAxisIndex: 0,
dataIndex: 0,
position: 'left',
color: lfxColors.brand[500]
}
]);
const configOverride = computed(() => ({
xAxis: {
axisLabel: {
formatter: axisLabelFormatter('MMM dd')
}
}
}));
const lineChartConfig = computed(() => getBarChartConfig(
chartData.value,
chartSeries.value,
configOverride.value
));

watch(error, (err) => {
if (err) {
showToast(
`Error fetching active contributors: ${error.value?.statusMessage}`,
ToastTypesEnum.negative,
undefined,
10000
);
}
});
</script>

<script lang="ts">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,116 @@
<h3 class="text-heading-3 font-semibold font-secondary pb-3">
Stars
</h3>
<p class="text-body-2 text-neutral-500">
<p class="text-body-2 text-neutral-500 mb-6">
Active contributor is an individual performing tasks such as commits,
issues, or pull requests during the selected time period.
</p>
<hr>
<section class="mt-5">
<div v-if="status === 'success'" class="flex flex-row gap-4 items-center mb-6">
<div class="text-data-display-1">{{ formatNumber(summary.current) }}</div>
<lfx-delta-display :summary="summary" icon="circle-arrow-up-right" icon-type="solid" />
</div>

<lfx-tabs :tabs="tabs" :model-value="activeTab" @update:model-value="activeTab = $event" />
<div class="w-full h-[330px] mt-4">
<lfx-chart v-if="status !== 'pending'" :config="lineChartConfig" />
<lfx-spinner v-else />
</div>
</section>
</lfx-card>
</template>

<script setup lang="ts">
import LfxCard from "~/components/uikit/card/card.vue";
import { useFetch, useRoute } from 'nuxt/app';
import { ref, computed, watch } from 'vue';
import type { StarsData } from './types/popularity.types';
import type { Summary } from '~/components/shared/types/summary.types';
import LfxCard from '~/components/uikit/card/card.vue';
import LfxDeltaDisplay from '~/components/uikit/delta-display/delta-display.vue';
import LfxTabs from '~/components/uikit/tabs/tabs.vue';
import { convertToChartData } from '~/components/uikit/chart/helpers/chart-helpers';
import type {
ChartData,
RawChartData,
ChartSeries
} from '~/components/uikit/chart/types/ChartTypes';
import LfxChart from '~/components/uikit/chart/chart.vue';
import { getLineAreaChartConfig } from '~/components/uikit/chart/configs/line.area.chart';
import { lfxColors } from '~/config/styles/colors';
import { axisLabelFormatter } from '~/components/uikit/chart/helpers/formatters';
import useToastService from '~/components/uikit/toast/toast.service';
import { ToastTypesEnum } from '~/components/uikit/toast/types/toast.types';
import LfxSpinner from '~/components/uikit/spinner/spinner.vue';
import { formatNumber } from '~/components/shared/utils/formatter';

const props = withDefaults(
defineProps<{
timePeriod?: string;
}>(),
{
timePeriod: '90d'
}
);

const { showToast } = useToastService();

const activeTab = ref('cumulative');
const route = useRoute();

const { data, status, error } = useFetch(
() => `/api/projects/popularity/stars?type=${activeTab.value}&project=${route.params.slug
}&repository=${route.params.name || ''}&time-period=${props.timePeriod}`
);

const stars = computed<StarsData>(() => data.value as StarsData);

const summary = computed<Summary>(() => stars.value.summary);
const chartData = computed<ChartData[]>(
// convert the data to chart data
() => convertToChartData(stars.value.data as RawChartData[], 'dateFrom', [
'stars'
])
);

const tabs = [
{ label: 'Cumulative', value: 'cumulative' },
{ label: 'New', value: 'new' }
];

const chartSeries = ref<ChartSeries[]>([
{
name: 'Stars',
type: 'line',
yAxisIndex: 0,
dataIndex: 0,
position: 'left',
color: lfxColors.brand[500]
}
]);
const configOverride = computed(() => ({
xAxis: {
axisLabel: {
formatter: axisLabelFormatter('MMM dd')
}
}
}));
const lineChartConfig = computed(() => getLineAreaChartConfig(
chartData.value,
chartSeries.value,
configOverride.value
));

watch(error, (err) => {
if (err) {
showToast(
`Error fetching active contributors: ${error.value?.statusMessage}`,
ToastTypesEnum.negative,
undefined,
10000
);
}
});
</script>

<script lang="ts">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Summary } from '~/components/shared/types/summary.types';

export interface StarsData {
summary: Summary;
data: {
dateFrom: string;
dateTo: string;
stars: number;
}[];
}

export interface ForksData {
summary: Summary;
data: {
dateFrom: string;
dateTo: string;
forks: number;
}[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const defaultLineOption: ECOption = {
grid: {
top: '5%',
left: '8%',
right: '1%'
right: '1%',
bottom: '15%'
},
xAxis: {
...defaultOption.xAxis,
Expand Down
39 changes: 39 additions & 0 deletions frontend/server/api/projects/popularity/forks.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { cumulative, newStars } from '~~/server/mocks/forks.mock';

/**
* Frontend expects the data to be in the following format:
* {
* summary: {
* current: number; // current value
* previous: number; // previous value
* percentageChange: number; // percentage change (return as actual percentage ex: 2.3 percent)
* changeValue: number; // change value
* periodFrom: string; // period from
* periodTo: string; // period to
* },
* data: {
* dateFrom: string; // ISO 8601 date string - start of the bucket. Based on the interval
* dateTo: string; // ISO 8601 date string - end of the bucket. Based on the interval
* forks: number; // count of forks
* }[];
* }
*/
/**
* Query params:
* - type: 'cumulative' | 'new'
* - project: string
* - repository: string
* - time-period: string // This is isn't defined yet, but we'll add '90d', '1y', '5y' for now
*/
export default defineEventHandler(async (event) => {
const query = getQuery(event);
let data;

if (query.type === 'cumulative') {
data = cumulative;
} else {
data = newStars;
}
Comment on lines +34 to +36
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 incorrect mock data usage in the else block.

The else block is using newStars instead of newForks, which appears to be a copy-paste error.

-    data = newStars;
+    data = newForks;
📝 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
} else {
data = newStars;
}
} else {
- data = newStars;
+ data = newForks;
}


return data;
});
Comment on lines +28 to +39
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 query parameter validation and error handling.

The endpoint lacks validation for required query parameters and error handling for invalid inputs. Consider adding the following improvements:

  1. Validate required query parameters
  2. Add type safety for query parameters
  3. Handle invalid query parameter values
 export default defineEventHandler(async (event) => {
   const query = getQuery(event);
+  const { type, project, repository } = query;
+
+  if (!type || !project || !repository) {
+    throw createError({
+      statusCode: 400,
+      message: 'Missing required query parameters: type, project, repository'
+    });
+  }
+
+  if (type !== 'cumulative' && type !== 'new') {
+    throw createError({
+      statusCode: 400,
+      message: 'Invalid type parameter. Must be either "cumulative" or "new"'
+    });
+  }
+
   let data;

-  if (query.type === 'cumulative') {
+  if (type === 'cumulative') {
     data = cumulative;
   } else {
-    data = newStars;
+    data = newForks;
   }

   return data;
 });
📝 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);
let data;
if (query.type === 'cumulative') {
data = cumulative;
} else {
data = newStars;
}
return data;
});
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const { type, project, repository } = query;
if (!type || !project || !repository) {
throw createError({
statusCode: 400,
message: 'Missing required query parameters: type, project, repository'
});
}
if (type !== 'cumulative' && type !== 'new') {
throw createError({
statusCode: 400,
message: 'Invalid type parameter. Must be either "cumulative" or "new"'
});
}
let data;
if (type === 'cumulative') {
data = cumulative;
} else {
data = newForks;
}
return data;
});

39 changes: 39 additions & 0 deletions frontend/server/api/projects/popularity/stars.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { cumulative, newStars } from '~~/server/mocks/stars.mock';

/**
* Frontend expects the data to be in the following format:
* {
* summary: {
* current: number; // current value
* previous: number; // previous value
* percentageChange: number; // percentage change (return as actual percentage ex: 2.3 percent)
* changeValue: number; // change value
* periodFrom: string; // period from
* periodTo: string; // period to
* },
* data: {
* dateFrom: string; // ISO 8601 date string - start of the bucket. Based on the interval
* dateTo: string; // ISO 8601 date string - end of the bucket. Based on the interval
* stars: number; // count of stars
* }[];
* }
*/
/**
* Query params:
* - type: 'cumulative' | 'new'
* - project: string
* - repository: string
* - time-period: string // This is isn't defined yet, but we'll add '90d', '1y', '5y' for now
*/
export default defineEventHandler(async (event) => {
const query = getQuery(event);
let data;

if (query.type === 'cumulative') {
data = cumulative;
} else {
data = newStars;
}

return data;
});
Loading