Skip to content

Commit

Permalink
feat(api): /widgets/indicator now returns the found widget width data
Browse files Browse the repository at this point in the history
  • Loading branch information
alepefe-vizz committed Oct 24, 2024
1 parent c1a3267 commit 6837370
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 114 deletions.
98 changes: 0 additions & 98 deletions api/src/infrastructure/postgres-survey-data.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,102 +128,4 @@ export class PostgresSurveyDataRepository implements ISurveyDataRepository {

widget.data = widgetData;
}

// public async addSurveyDataToSections(
// sections: SectionWithDataWidget[],
// filters?: WidgetDataFilters,
// ): Promise<SectionWithDataWidget[]> {
// const answersTable: string = 'survey_answers';
// let filterClause: string;

// if (filters !== undefined) {
// filterClause = this.sqlAdapter.generateSqlFromWidgetDataFilters(filters);
// }

// const widgetDataPromises = [];
// for (let sectionIdx = 0; sectionIdx < sections.length; sectionIdx++) {
// const section = sections[sectionIdx];
// const baseWidgets = section.baseWidgets;
// for (let widgetIdx = 0; widgetIdx < baseWidgets.length; widgetIdx++) {
// const widget = baseWidgets[widgetIdx];
// widgetDataPromises.push(
// this.appendBaseWidgetData(answersTable, filterClause, widget),
// );
// }
// }

// await Promise.all(widgetDataPromises);
// return sections;
// }

// private async appendBaseWidgetData(
// answersTable: string,
// filterClause: string,
// widget: BaseWidgetWithData,
// ): Promise<void> {
// const { indicator } = widget;
// // Edge cases here. Total surveys and total countries.
// if (indicator === 'total-surveys') {
// const filteredCount = `SELECT COUNT(count)::integer as count FROM (SELECT COUNT(DISTINCT survey_id) FROM ${answersTable} ${filterClause} GROUP BY survey_id) AS survey_count`;
// const totalCount = `SELECT COUNT(count)::integer as count FROM (SELECT COUNT(DISTINCT survey_id) FROM ${answersTable} GROUP BY survey_id) AS survey_count`;
// const [[{ count: value }], [{ count: total }]] = await Promise.all([
// this.dataSource.query(filteredCount),
// this.dataSource.query(totalCount),
// ]);
// widget.data = { counter: { value, total } };
// return;
// }
// if (indicator === 'total-countries') {
// const filteredCount = `SELECT COUNT(DISTINCT country_code)::integer as "count" FROM ${answersTable} ${filterClause}`;
// const totalCount = `SELECT COUNT(DISTINCT country_code)::integer as "count" FROM ${answersTable};`;
// const [[{ count: value }], [{ count: total }]] = await Promise.all([
// this.dataSource.query(filteredCount),
// this.dataSource.query(totalCount),
// ]);
// widget.data = { counter: { value, total } };
// return;
// }

// const totalsSql = `SELECT answer as "key", count(answer)::integer as "count", SUM(COUNT(answer)) OVER ()::integer AS total FROM ${answersTable} ${this.sqlAdapter.appendExpressionToFilterClause(filterClause, `question_indicator = '${indicator}'`)} GROUP BY answer`;
// const totalsResult: { key: string; count: number; total: number }[] =
// await this.dataSource.query(totalsSql);

// if (totalsResult.length === 0) {
// widget.data = {};
// return;
// }

// const widgetData: WidgetData = {};
// const {
// supportsChart,
// supportsSingleValue,
// supportsMap,
// supportsNavigation,
// } = WidgetUtils.getSupportedVisualizations(widget);

// if (supportsChart) {
// const arr: WidgetChartData = [];

// for (let rowIdx = 0; rowIdx < totalsResult.length; rowIdx++) {
// const res = totalsResult[rowIdx];
// arr.push({ label: res.key, value: res.count, total: res.total });
// }

// widgetData.chart = arr;
// }

// if (supportsSingleValue) {
// // TODO: Add WidgetCounterData
// }

// if (supportsMap) {
// // TODO: Add WidgetMapData
// }

// if (supportsNavigation) {
// // TODO: Add WidgetNavigationData
// }

// widget.data = widgetData;
// }
}
5 changes: 4 additions & 1 deletion api/src/modules/widgets/widgets.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ export class WidgetsController {
@TsRestHandler(c.getWidget)
async getWidget(): Promise<any> {
return tsRestHandler(c.getWidget, async ({ params: { id }, query }) => {
const widget = await this.widgetsService.getById(id, query);
const widget = await this.widgetsService.findWidgetWithDataById(
id,
query,
);
return { body: { data: widget }, status: HttpStatus.OK };
});
}
Expand Down
10 changes: 9 additions & 1 deletion api/src/modules/widgets/widgets.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ import { PageFiltersController } from '@api/modules/widgets/page-filters.control
import { PageFilter } from '@shared/dto/widgets/page-filter.entity';
import { WidgetsController } from '@api/modules/widgets/widgets.controller';
import { WidgetsService } from '@api/modules/widgets/widgets.service';
import { PostgresSurveyDataRepository } from '@api/infrastructure/postgres-survey-data.repository';
import { SQLAdapter } from '@api/infrastructure/sql-adapter';

@Module({
imports: [
TypeOrmModule.forFeature([BaseWidget, CustomWidget, PageFilter]),
forwardRef(() => AuthModule),
],
providers: [CustomWidgetService, PageFiltersService, WidgetsService],
providers: [
SQLAdapter,
{ provide: 'SurveyDataRepository', useClass: PostgresSurveyDataRepository },
CustomWidgetService,
PageFiltersService,
WidgetsService,
],
controllers: [
CustomWidgetsController,
PageFiltersController,
Expand Down
25 changes: 23 additions & 2 deletions api/src/modules/widgets/widgets.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { AppBaseService } from '@api/utils/app-base.service';
import { AppInfoDTO } from '@api/utils/info.dto';
import { Injectable, Logger } from '@nestjs/common';
import { Inject, Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { BaseWidget } from '@shared/dto/widgets/base-widget.entity';
import { Repository, SelectQueryBuilder } from 'typeorm';
import { FetchSpecification } from 'nestjs-base-service';
import { WidgetVisualisationFilters } from '@shared/schemas/widget-visualisation-filters.schema';
import { BaseWidgetWithData } from '@shared/dto/widgets/base-widget-data.interface';
import { WidgetDataFiltersSchema } from '@shared/schemas/widget-data-filters.schema';
import { ISurveyDataRepository } from '@api/infrastructure/survey-data-repository.interface';

@Injectable()
export class WidgetsService extends AppBaseService<
Expand All @@ -19,8 +22,10 @@ export class WidgetsService extends AppBaseService<
protected readonly logger: Logger,
@InjectRepository(BaseWidget)
private baseWidgetRepository: Repository<BaseWidget>,
@Inject('SurveyDataRepository')
private readonly surveyDataRepository: ISurveyDataRepository,
) {
super(baseWidgetRepository, 'baseWidget', 'basedWidgets');
super(baseWidgetRepository, 'baseWidget', 'baseWidgets');
}

async extendFindAllQuery(
Expand All @@ -46,4 +51,20 @@ export class WidgetsService extends AppBaseService<
);
return query;
}

public async findWidgetWithDataById(
id: string,
query: FetchSpecification & WidgetDataFiltersSchema,
): Promise<BaseWidgetWithData> {
const widget = await this.baseWidgetRepository.findOneBy({ indicator: id });
const { filters } = query;

const baseWidgetWithData =
await this.surveyDataRepository.addSurveyDataToBaseWidget(
widget,
filters,
);

return baseWidgetWithData;
}
}
2 changes: 1 addition & 1 deletion api/test/e2e/sections/sections-crud.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('Page Sections API', () => {
const res = await testManager
.request()
.get(
'/sections?filters[0][name]=eu-member-state&filters[0][operator]==&filters[0][values]=Spain&filters[0][values]=Belgium',
'/sections?filters[0][name]=eu-member-state&filters[0][operator]==&filters[0][values][0]=Belgium',
); // Implicit ?include[]=baseWidgets&sort[]=order&sort[]=baseWidget.sectionOrder

// Then
Expand Down
67 changes: 62 additions & 5 deletions api/test/e2e/widgets/base-widgets.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TestManager } from 'api/test/utils/test-manager';
import { WidgetVisualizationsType } from '@shared/dto/widgets/widget-visualizations.constants';
import { DataSourceManager } from '@api/infrastructure/data-source-manager';
import { BaseWidget } from '@shared/dto/widgets/base-widget.entity';
import ObjectUtils from 'api/test/utils/object.utils';

describe('Base Widgets', () => {
let testManager: TestManager<unknown>;
Expand Down Expand Up @@ -56,11 +57,6 @@ describe('Base Widgets', () => {
const mocks = testManager.mocks();
for (const [i, vizz] of vizzes.entries()) {
const indicator = `indicator-${i}`;
// To make sure we satify the FK_constraint
await mocks.createQuestionIndicatorMap({
indicator,
question: indicator,
});
await mocks.createBaseWidget({
indicator,
visualisations: vizz as WidgetVisualizationsType[],
Expand Down Expand Up @@ -94,4 +90,65 @@ describe('Base Widgets', () => {

expect(noneFound.body.data).toHaveLength(0);
});

it('Should retrieve a widget with its data by its id (indicator)', async () => {
// Given
const dataSourceManager = testManager.testApp.get(DataSourceManager);
await dataSourceManager.loadQuestionIndicatorMap();
await dataSourceManager.loadSurveyData(
`${__dirname}/../../data/surveys.json`,
);

const indicator = 'total-surveys';
const mocks = testManager.mocks();
const createdWidget = await mocks.createBaseWidget({
indicator,
});

// When
const result = await testManager.request().get(`/widgets/${indicator}`);
const returnedWidget = result.body.data;

const createdWidgetWithData = {
...ObjectUtils.normalizeDates(createdWidget),
data: {
counter: { value: 5, total: 5 },
},
};
// Then
expect(returnedWidget).toStrictEqual(createdWidgetWithData);
});

it('Should retrieve a widget with its filtered data by its id (indicator)', async () => {
// Given
const dataSourceManager = testManager.testApp.get(DataSourceManager);
await dataSourceManager.loadQuestionIndicatorMap();
await dataSourceManager.loadSurveyData(
`${__dirname}/../../data/surveys.json`,
);

const indicator = 'total-surveys';
const mocks = testManager.mocks();
const createdWidget = await mocks.createBaseWidget({
indicator,
});

// When
const result = await testManager
.request()
.get(
`/widgets/${indicator}?filters[0][name]=eu-member-state&filters[0][operator]==&filters[0][values][0]=Belgium`,
);

const returnedWidget = result.body.data;

const createdWidgetWithData = {
...ObjectUtils.normalizeDates(createdWidget),
data: {
counter: { value: 2, total: 5 },
},
};
// Then
expect(returnedWidget).toStrictEqual(createdWidgetWithData);
});
});
5 changes: 2 additions & 3 deletions shared/contracts/base-widgets.contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { generateEntityQuerySchema } from '@shared/schemas/query-param.schema';
import { BaseWidget } from '@shared/dto/widgets/base-widget.entity';
import { WidgetVisualizationFilterSchema } from '@shared/schemas/widget-visualisation-filters.schema';
import { WidgetDataFiltersSchema } from '@shared/schemas/widget-data-filters.schema';

const contract = initContract();
export const widgetsContract = contract.router({
Expand All @@ -29,9 +30,7 @@ export const widgetsContract = contract.router({
pathParams: z.object({
id: z.coerce.string(),
}),
query: generateEntityQuerySchema(BaseWidget).merge(
WidgetVisualizationFilterSchema,
),
query: generateEntityQuerySchema(BaseWidget).merge(WidgetDataFiltersSchema),
responses: {
200: contract.type<ApiResponse<BaseWidget>>(),
400: contract.type<JSONAPIError>(),
Expand Down
5 changes: 2 additions & 3 deletions shared/lib/e2e-test-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,8 @@ export class E2eTestManager {
createSection: (additionalData?: DeepPartial<Section>) =>
createSection(this.getDataSource(), additionalData),
createBaseWidget: (
additionalData?: DeepPartial<BaseWidget>,
): Promise<BaseWidget> =>
createBaseWidget(this.getDataSource(), additionalData),
data: DeepPartial<BaseWidget> & { indicator: string },
): Promise<BaseWidget> => createBaseWidget(this.getDataSource(), data),
createCustomWidget: (
additionalData?: DeepPartial<CustomWidget>,
): Promise<CustomWidget> =>
Expand Down

0 comments on commit 6837370

Please sign in to comment.