diff --git a/openapi.json b/openapi.json index e5201314..6170af02 100644 --- a/openapi.json +++ b/openapi.json @@ -6103,7 +6103,17 @@ "/analytics/applications": { "get": { "operationId": "analytics_getApplicationAnalytics", - "parameters": [], + "parameters": [ + { + "name": "hackathonId", + "required": false, + "in": "query", + "description": "The hackathon ID to fetch application metrics for. Defaults to the active hackathon.", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "", diff --git a/packages/api-client/src/generated/analytics/analytics.ts b/packages/api-client/src/generated/analytics/analytics.ts index eed75f19..2c5562ec 100644 --- a/packages/api-client/src/generated/analytics/analytics.ts +++ b/packages/api-client/src/generated/analytics/analytics.ts @@ -23,6 +23,7 @@ import type { import type { AnalyticsApplicationsResponse, AnalyticsEventsResponse, + AnalyticsGetApplicationAnalyticsParams, AnalyticsGetCheckInsParams, AnalyticsScansResponse, AnalyticsSummaryResponse, @@ -459,20 +460,27 @@ export function useAnalyticsGetCheckIns { +export const getAnalyticsGetApplicationAnalyticsUrl = (params?: AnalyticsGetApplicationAnalyticsParams,) => { + const normalizedParams = new URLSearchParams(); + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : String(value)) + } + }); + const stringifiedParams = normalizedParams.toString(); - return `/analytics/applications` + return stringifiedParams.length > 0 ? `/analytics/applications?${stringifiedParams}` : `/analytics/applications` } /** * @summary Get application metrics for each event */ -export const analyticsGetApplicationAnalytics = async ( options?: Parameters[1]): Promise => { +export const analyticsGetApplicationAnalytics = async (params?: AnalyticsGetApplicationAnalyticsParams, options?: Parameters[1]): Promise => { - return customFetch(getAnalyticsGetApplicationAnalyticsUrl(), + return customFetch(getAnalyticsGetApplicationAnalyticsUrl(params), { ...options, method: 'GET' @@ -485,23 +493,23 @@ export const analyticsGetApplicationAnalytics = async ( options?: Parameters { +export const getAnalyticsGetApplicationAnalyticsQueryKey = (params?: AnalyticsGetApplicationAnalyticsParams,) => { return [ - `/analytics/applications` + `/analytics/applications`, ...(params ? [params] : []) ] as const; } -export const getAnalyticsGetApplicationAnalyticsQueryOptions = >, TError = ExceptionResponse>( options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} +export const getAnalyticsGetApplicationAnalyticsQueryOptions = >, TError = ExceptionResponse>(params?: AnalyticsGetApplicationAnalyticsParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} ) => { const {query: queryOptions, request: requestOptions} = options ?? {}; - const queryKey = queryOptions?.queryKey ?? getAnalyticsGetApplicationAnalyticsQueryKey(); + const queryKey = queryOptions?.queryKey ?? getAnalyticsGetApplicationAnalyticsQueryKey(params); - const queryFn: QueryFunction>> = ({ signal }) => analyticsGetApplicationAnalytics({ signal, ...requestOptions }); + const queryFn: QueryFunction>> = ({ signal }) => analyticsGetApplicationAnalytics(params, { signal, ...requestOptions }); @@ -515,7 +523,7 @@ export type AnalyticsGetApplicationAnalyticsQueryError = ExceptionResponse export function useAnalyticsGetApplicationAnalytics>, TError = ExceptionResponse>( - options: { query:Partial>, TError, TData>> & Pick< + params: undefined | AnalyticsGetApplicationAnalyticsParams, options: { query:Partial>, TError, TData>> & Pick< DefinedInitialDataOptions< Awaited>, TError, @@ -525,7 +533,7 @@ export function useAnalyticsGetApplicationAnalytics & { queryKey: DataTag } export function useAnalyticsGetApplicationAnalytics>, TError = ExceptionResponse>( - options?: { query?:Partial>, TError, TData>> & Pick< + params?: AnalyticsGetApplicationAnalyticsParams, options?: { query?:Partial>, TError, TData>> & Pick< UndefinedInitialDataOptions< Awaited>, TError, @@ -535,7 +543,7 @@ export function useAnalyticsGetApplicationAnalytics & { queryKey: DataTag } export function useAnalyticsGetApplicationAnalytics>, TError = ExceptionResponse>( - options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} + params?: AnalyticsGetApplicationAnalyticsParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} , queryClient?: QueryClient ): UseQueryResult & { queryKey: DataTag } /** @@ -543,11 +551,11 @@ export function useAnalyticsGetApplicationAnalytics>, TError = ExceptionResponse>( - options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} + params?: AnalyticsGetApplicationAnalyticsParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter} , queryClient?: QueryClient ): UseQueryResult & { queryKey: DataTag } { - const queryOptions = getAnalyticsGetApplicationAnalyticsQueryOptions(options) + const queryOptions = getAnalyticsGetApplicationAnalyticsQueryOptions(params,options) const query = useQuery(queryOptions, queryClient) as UseQueryResult & { queryKey: DataTag }; diff --git a/packages/api-client/src/generated/model/analyticsGetApplicationAnalyticsParams.ts b/packages/api-client/src/generated/model/analyticsGetApplicationAnalyticsParams.ts new file mode 100644 index 00000000..feabd798 --- /dev/null +++ b/packages/api-client/src/generated/model/analyticsGetApplicationAnalyticsParams.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval 🍺 + * Do not edit manually. + * HackPSU Documentation + * Official HackPSU API V3 + * OpenAPI spec version: 3.0 + */ + +export type AnalyticsGetApplicationAnalyticsParams = { +/** + * The hackathon ID to fetch application metrics for. Defaults to the active hackathon. + */ +hackathonId?: string; +}; diff --git a/packages/api-client/src/generated/model/index.ts b/packages/api-client/src/generated/model/index.ts index 3930fe4c..32a2cd4f 100644 --- a/packages/api-client/src/generated/model/index.ts +++ b/packages/api-client/src/generated/model/index.ts @@ -13,6 +13,7 @@ export * from './allergenCounts'; export * from './analyticsApplicationsResponse'; export * from './analyticsEventsResponse'; export * from './analyticsEventsScansEntity'; +export * from './analyticsGetApplicationAnalyticsParams'; export * from './analyticsGetCheckInsParams'; export * from './analyticsScansResponse'; export * from './analyticsSummaryResponse'; diff --git a/src/modules/analytics/analytics.controller.ts b/src/modules/analytics/analytics.controller.ts index 3682b591..6d86a5be 100644 --- a/src/modules/analytics/analytics.controller.ts +++ b/src/modules/analytics/analytics.controller.ts @@ -440,6 +440,7 @@ export class AnalyticsController { private async calculateApplicationMetrics( applications: any[], + hackathonId?: string, ): Promise { const confirmedApplicants = applications.filter( (app) => app.applicationStatus === "confirmed", @@ -458,7 +459,7 @@ export class AnalyticsController { const confirmedAndScannedApplicants = await this.scanRepo .findAll() - .byHackathon() + .byHackathon(hackathonId) .joinRelated("event") .whereIn( "user_id", @@ -490,6 +491,13 @@ export class AnalyticsController { @Get("/applications") @Roles(Role.NONE) + @ApiQuery({ + name: "hackathonId", + required: false, + type: String, + description: + "The hackathon ID to fetch application metrics for. Defaults to the active hackathon.", + }) @ApiDoc({ summary: "Get application metrics for each event", auth: Role.TEAM, @@ -497,28 +505,36 @@ export class AnalyticsController { ok: { type: AnalyticsApplicationsResponse }, }, }) - async getApplicationAnalytics() { - const PENN_STATE_UNIVERSITY = - "The Pennsylvania State University - Main Campus"; - - // Get all registrations with user information for the active hackathon + async getApplicationAnalytics(@Query("hackathonId") hackathonId?: string) { + // Matches every Penn State campus, not just Main Campus + const PENN_STATE_PREFIX = "The Pennsylvania State University"; + const isPennState = (university?: string | null) => + university?.startsWith(PENN_STATE_PREFIX) ?? false; + + // Get all registrations with user information for the requested hackathon + // (falls back to the active hackathon when no ID is provided) const applications = await this.registrationRepo .findAll() - .byHackathon() + .byHackathon(hackathonId) .withGraphFetched("user"); // Separate Penn State and Other students - const pennStateApps = applications.filter( - (app) => app.user.university === PENN_STATE_UNIVERSITY, + const pennStateApps = applications.filter((app) => + isPennState(app.user?.university), ); const otherApps = applications.filter( - (app) => app.user.university !== PENN_STATE_UNIVERSITY, + (app) => !isPennState(app.user?.university), ); - const pennStateMetrics = - await this.calculateApplicationMetrics(pennStateApps); + const pennStateMetrics = await this.calculateApplicationMetrics( + pennStateApps, + hackathonId, + ); - const otherMetrics = await this.calculateApplicationMetrics(otherApps); + const otherMetrics = await this.calculateApplicationMetrics( + otherApps, + hackathonId, + ); return { pennState: pennStateMetrics, diff --git a/yarn.lock b/yarn.lock index 354fe2d2..57a518e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5067,11 +5067,11 @@ gtoken@^8.0.0: jws "^4.0.0" handlebars@^4.7.8, handlebars@^4.7.9: - version "4.7.9" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.9.tgz#6f139082ab58dc4e5a0e51efe7db5ae890d56a0f" - integrity sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ== + version "4.7.10" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.10.tgz#12419ea1b41391b3ffcf003f2164dcbce090c417" + integrity sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg== dependencies: - minimist "^1.2.5" + minimist "^1.2.8" neo-async "^2.6.2" source-map "^0.6.1" wordwrap "^1.0.0" @@ -6442,7 +6442,7 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.2" -minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==