import { Injectable } from '@angular/core'; import { CLIENT_SETTINGS_SERVICE_MOCK } from '@core/mocks/client-settings.service.mock'; import { CURRENCY_SERVICE_MOCK } from '@core/mocks/currency.service.mock'; import { GCMockModule } from '@core/mocks/gc-module.mock'; import { SYSTEM_TAGS_SERVICE_MOCK } from '@core/mocks/system-tags.service.mock'; import { AdHocReportingAPI } from '@core/typings/api/ad-hoc-reporting.typing'; import { ReferenceFieldAPI } from '@core/typings/api/reference-fields.typing'; import { ApplicationStatuses } from '@core/typings/status.typing'; import { AdHocReportingUI } from '@core/typings/ui/ad-hoc-reporting.typing'; import { ReferenceFieldsUI } from '@core/typings/ui/reference-fields.typing'; import { AudienceMember } from '@features/audience/audience.typing'; import { FormAudience } from '@features/configure-forms/form.typing'; import { StandardProductDashboard } from '@features/platform-admin/standard-product-configuration/standard-product-configuration.typing'; import { AdHocReportingDefinitions } from '@features/reporting/services/ad-hoc-reporting-definitions.service'; import { SystemTagsService } from '@features/system-tags/system-tags.service'; import { APIResultData, ArrayHelpersService, FilterColumn, FilterModalTypes, PaginationOptions, SimpleStringMap, SwitchState } from '@yourcause/common'; import { BeforeEach, Spec, TestCase } from '@yourcause/test-decorators'; import { DescribeAngularService } from '@yourcause/test-decorators/angular'; import { expect, spy } from 'chai'; import { TooltipItem } from 'chart.js'; import { DashboardsService, OTHER_OPTION } from './dashboards.service'; import { Dashboards, GCDashboards } from './dashboards.typing'; @Injectable({ providedIn: 'root' }) @DescribeAngularService(DashboardsService, { imports: [ GCMockModule ], providers: [ CURRENCY_SERVICE_MOCK, SYSTEM_TAGS_SERVICE_MOCK, CLIENT_SETTINGS_SERVICE_MOCK ] }) export class DashboardsServiceSpec implements Spec { widgetId = 1; refFieldTextKey = 'refFieldKey1'; refFieldNumberKey = 'refFieldKey2'; formId = 1235; userSavedReportColumns: AdHocReportingAPI.UserSavedReportColumn[] = [{ columnName: 'application.id', sortType: AdHocReportingAPI.SortTypes.NoSort, sortPriority: null, userSavedFilterColumns: [], displayName: '', isChartGroupingColumn: false, isChartSubGroupingColumn: false, isChartAggregate: true, chartAggregateType: AdHocReportingAPI.ChartAggregateType.Count, referenceFieldId: null }, { columnName: 'grantProgram.name', sortType: AdHocReportingAPI.SortTypes.NoSort, sortPriority: null, userSavedFilterColumns: [{ id: 1, filterType: 'eq', filterValue: 123 }], displayName: '', isChartGroupingColumn: true, isChartSubGroupingColumn: false, isChartAggregate: false, chartAggregateType: null, referenceFieldId: null }, { columnName: 'application.applicationStatus', sortType: AdHocReportingAPI.SortTypes.NoSort, sortPriority: 1, userSavedFilterColumns: [], displayName: '', isChartGroupingColumn: true, isChartSubGroupingColumn: true, isChartAggregate: false, chartAggregateType: null, referenceFieldId: null }, { columnName: `category.1.${this.refFieldTextKey}`, sortType: AdHocReportingAPI.SortTypes.NoSort, sortPriority: 1, userSavedFilterColumns: [], displayName: '', isChartGroupingColumn: false, isChartSubGroupingColumn: false, isChartAggregate: false, chartAggregateType: null, referenceFieldId: null }, { columnName: `category.1.${this.refFieldNumberKey}`, sortType: AdHocReportingAPI.SortTypes.NoSort, sortPriority: 1, userSavedFilterColumns: [], displayName: '', isChartGroupingColumn: false, isChartSubGroupingColumn: false, isChartAggregate: false, chartAggregateType: null, referenceFieldId: null }]; widget: GCDashboards.WidgetConfigFromApi = { id: this.widgetId, userSavedReportColumns: this.userSavedReportColumns, name: '', description: '', othersMayModify: true, othersMayView: true, reportModelType: AdHocReportingAPI.AdHocReportModelType.Application, reportType: AdHocReportingAPI.AdHocReportType.Chart, chartType: AdHocReportingAPI.ChartType.Bar, chartConfig: '{}', forms: [], advancedFilterColumns: [], useLogicalOperatorAnd: false, chartMaxRows: 10, chartIncludeOtherAggregate: true }; widgetEditMap: SimpleStringMap = { [this.widgetId]: this.widget }; mappedChartData: GCDashboards.ChartDataResult[] = [{ label: '2313232', subLabel: 'Awaiting review', subValue: 1, xValue: '2313232', yValue: 1, count: 1 }, { label: '2313232', subLabel: 'In progress', subValue: 2, xValue: '2313232', yValue: 1, count: 1 }, { label: 'File Upload Test', subLabel: 'In progress', subValue: 2, xValue: 'File Upload Test', yValue: 1, count: 1 }, { label: 'AmountRequestedCopyTest Program', subLabel: 'Awaiting review', subValue: 1, xValue: 'AmountRequestedCopyTest Program', yValue: 1, count: 1 }]; categoryId = 1; formFieldColumn1 = `category.${this.categoryId}.${this.refFieldTextKey}`; formFieldColumn2 = `category.${this.categoryId}.${this.refFieldNumberKey}`; allColumns: AdHocReportingUI.ColumnImplementation[] = [{ sortType: 1, visibleInReport: true, filterColumn: { columnName: this.formFieldColumn1, filters: [] }, columnNameOverride: '', definition: { column: this.refFieldTextKey, parentBucket: `category.${this.categoryId}`, parentBucketName: '1st Category', type: 'text', format: null, display: 'What kind of disaster was it?', i18nKey: null, isReferenceField: true } }, { sortType: 1, visibleInReport: true, filterColumn: { columnName: this.formFieldColumn2, filters: [] }, columnNameOverride: '', definition: { column: this.refFieldNumberKey, parentBucket: `category.${this.categoryId}`, parentBucketName: '1st Category', type: 'text', format: null, display: 'What kind of disaster was it?', i18nKey: null, isReferenceField: true } }, { sortType: 1, visibleInReport: true, filterColumn: { columnName: 'grantProgram.name', filters: [] }, columnNameOverride: '', definition: { column: 'name', parentBucket: 'grantProgram', parentBucketName: 'Grant Program', type: 'text', format: null, display: 'Grant Program Name', i18nKey: null, isReferenceField: undefined, supportsGrouping: true } }, { sortType: 1, visibleInReport: true, filterColumn: { columnName: 'application.applicationStatus', filters: [] }, columnNameOverride: '', definition: { column: 'applicationStatus', parentBucket: 'application', parentBucketName: 'Application', type: 'text', format: null, display: 'Application Status', i18nKey: null, isReferenceField: undefined, supportsGrouping: true } }]; formFieldFilterColumn = `category.${this.categoryId}.${this.refFieldTextKey}`; filters: FilterColumn[] = [{ columnName: this.formFieldFilterColumn, filters: [{ filterType: 'eq', filterValue: 'SC' }] }]; numberField: ReferenceFieldAPI.ReferenceFieldDisplayModel = { aggregateType: null, customDataTableGuid: '', name: 'Text', description: '', type: ReferenceFieldsUI.ReferenceFieldTypes.Number, key: this.refFieldNumberKey, supportsMultiple: false, categoryId: this.categoryId, formAudience: FormAudience.APPLICANT, referenceFieldId: 1, formCount: 1, usedOnReports: false, createdBy: '', createDate: '', updatedBy: '', updateDate: '', parentReferenceFieldId: null, isEncrypted: false, isMasked: false, tableAllowsImport: false, isSingleResponse: true, isTableField: false, aggregateTableReferenceFieldId: null, referenceFieldTableId: null, standardComponentIsPublished: true, isStandardProductField: false, subsetCollectionType: null }; textField: ReferenceFieldAPI.ReferenceFieldDisplayModel = { aggregateType: null, customDataTableGuid: '', name: 'Text', description: '', type: ReferenceFieldsUI.ReferenceFieldTypes.TextField, key: this.refFieldTextKey, supportsMultiple: false, categoryId: this.categoryId, formAudience: FormAudience.APPLICANT, referenceFieldId: 1, formCount: 1, usedOnReports: false, createdBy: '', createDate: '', updatedBy: '', updateDate: '', parentReferenceFieldId: null, isEncrypted: false, isMasked: false, tableAllowsImport: false, isSingleResponse: true, isTableField: false, aggregateTableReferenceFieldId: null, referenceFieldTableId: null, standardComponentIsPublished: true, isStandardProductField: false, subsetCollectionType: null }; allRefFields: ReferenceFieldAPI.ReferenceFieldDisplayModel[] = [ this.textField, this.numberField ]; formatTooltipPayloadPieCount: AdHocReportingUI.FormatTooltipValuePayload = { isPie: true, isDollarAmount: false, isPercent: false, data: { datasets: [{ counts: [ 329, 230, 102, 24, 5 ], data: [ 329, 230, 102, 24, 5 ] }] } as any, count: 102, showCount: false, tooltipItem: { datasetIndex: 0, dataIndex: 2 } as TooltipItem, totalCount: 690, totalValue: 690 }; formatTooltipPayloadPieSum: AdHocReportingUI.FormatTooltipValuePayload = { isPie: true, isDollarAmount: true, isPercent: false, tooltipItem: { datasetIndex: 0, dataIndex: 2, label: '', chart: { data: { datasets: [{ data: [ 109544.5, 94499, 12600, 583, 1081 ], counts: [ 77, 13, 96, 7, 85 ] }] } as any } } as TooltipItem, data: { datasets: [{ data: [ 109544.5, 94499, 12600, 583, 1081 ], counts: [ 77, 13, 96, 7, 85 ] }] } as any, count: 96, showCount: true, totalCount: 278, totalValue: 218307.5 }; formatTooltipPayloadBarCount: AdHocReportingUI.FormatTooltipValuePayload = { isPie: false, isPercent: false, isDollarAmount: false, tooltipItem: { datasetIndex: 0, dataIndex: 0, label: '8', formattedValue: '8', raw: 8, chart: { data: { datasets: [{ data: [8], counts: [8] }] } as any } } as TooltipItem, data: { datasets: [{ data: [8], counts: [8] }] } as any, count: 8, showCount: false, totalCount: 8, totalValue: 8 }; formatTooltipPayloadBarSum: AdHocReportingUI.FormatTooltipValuePayload = { isPie: false, isPercent: false, isDollarAmount: true, tooltipItem: { datasetIndex: 0, dataIndex: 0, label: '2977', formattedValue: '2977', raw: 2977, chart: { data: { datasets: [{ data: [2977], counts: [5] }] } as any } } as TooltipItem, data: { datasets: [{ data: [2977], counts: [5] }] } as any, count: 5, showCount: true, totalCount: 5, totalValue: 2977 }; allWidgets: GCDashboards.SimpleWidgetConfigFromApi[] = [{ id: this.widgetId, name: '', description: '', othersMayModify: true, othersMayView: true, reportModelType: AdHocReportingAPI.AdHocReportModelType.Application, reportType: AdHocReportingAPI.AdHocReportType.Chart, chartType: AdHocReportingAPI.ChartType.Bar, chartConfig: '{}' }]; chartData: GCDashboards.DashboardDataResults = { results: [{ data: { application: { applicationStatus: ApplicationStatuses.Approved } as any, applicationInKind: null, award: null, awardInKind: null, payment: null, paymentInKind: null, budgets: null, fundingSources: null, todaysDate: null, table: null, fieldGroup: null, customForm: null, referenceFields: null, formData: [], formGroupingAttribute1: '', formGroupingAttribute2: '', referenceFieldGroupingAttribute1: '', referenceFieldGroupingAttribute2: '' } as GCDashboards.ChartResultData, aggregate: 27, count: 4 }], lastRefreshDate: '', otherAggregate: null, otherCount: null }; visibleDashboardId = 1; hiddenDashboardId = 2; dashboardTabs: GCDashboards.DashboardTab[] = [{ createdBy: 1, createdByFirstName: '', createdByLastName: '', createdDate: '', dashboardId: this.visibleDashboardId, dashboardType: GCDashboards.DashboardTypes.CUSTOM, isActive: true, isHidden: false, isDashboardOwner: false, name: '', numberOfUsersSharedWith: 0, order: 0, updatedBy: 0 }, { createdBy: 1, createdByFirstName: '', createdByLastName: '', createdDate: '', dashboardId: this.hiddenDashboardId, dashboardType: GCDashboards.DashboardTypes.CUSTOM, isActive: true, isHidden: true, isDashboardOwner: false, name: '', numberOfUsersSharedWith: 0, order: 0, updatedBy: 0 }]; standardProductDashboardList: StandardProductDashboard[] = [{ dashboardId: 123, name: 'string', order: 1, dashboardType: GCDashboards.DashboardTypes.CUSTOM, createdDate: 'asd', updatedDate: 'asd', createdBy: 'asd', updatedBy: 'asd', isDashboardOwner: false, numberOfUsersSharedWith: 0, isStandardDashboardPublished: true, isActive: true, createdByFirstName: 'asd', createdByLastName: 'asd', isHidden: true }]; constructor ( private adHocReportingDefinitions: AdHocReportingDefinitions, private arrayHelper: ArrayHelpersService, private systemTagsService: SystemTagsService ) { } @BeforeEach() mock (service: DashboardsService) { service['dashboardsResources'].getWidgetDetail = async () => { return this.widgetEditMap[this.widgetId]; }; service['dashboardsResources'].getAllWidgets = async () => { return this.allWidgets; }; service['dashboardsResources'].getChartData = async () => { return this.chartData; }; service['dashboardsResources'].getDashboardDetail = async () => { return { name: 'My Dash', order: 1, widgets: [this.widget] }; }; service['dashboardsResources'].getDashboardTabs = async () => { return this.dashboardTabs; }; service['adHocService'].setFormComponentMap({ [this.formId]: [{ key: this.refFieldTextKey, type: this.textField.type, label: '', referenceFieldKey: this.refFieldTextKey }, { key: this.refFieldNumberKey, type: this.numberField.type, label: '', referenceFieldKey: this.refFieldNumberKey }] }); service['adHocReportingMapper']['referenceFieldService'].setAllReferenceFields( this.allRefFields ); service['currencyService'].formatMoney = (amount: number) => { return '$' + amount; }; this.systemTagsService['arrayHelper'] = this.arrayHelper; service['adHocDefinitions'] = this.adHocReportingDefinitions; service['adHocDefinitions']['systemTagsService'] = this.systemTagsService; service['adHocDefinitions']['systemTagsService']['set']('summaries', []); service['policyService']['system']['canManageBudgets'] = () => { return true; }; service['policyService']['insights']['canViewBudgets'] = () => { return true; }; service['policyService']['system']['canManageFundingSources'] = () => { return true; }; service['policyService']['insights']['canViewFundingSources'] = () => { return true; }; service['standardProductConfigService']['standardProductResources']['getStandardProductDashboards'] = async () => { return this.standardProductDashboardList; }; service['standardProductConfigService']['policyService']['dataExport']['canManageDashboards'] = () => { return true; }; } @TestCase('should know when to prompt for unsaved changes') async shouldPromptForUnsavedChanges (service: DashboardsService) { service['set']('editing', true); service['set']('widgetEditMap', this.widgetEditMap); service['set']('promptingForUnsavedChanges', true); let shouldPrompt = service.shouldPromptForUnsavedChanges(); expect(shouldPrompt).to.be.false; service['set']('promptingForUnsavedChanges', false); shouldPrompt = service.shouldPromptForUnsavedChanges(); expect(shouldPrompt).to.be.true; } @TestCase('should be able to extract grouping columns') async extractGroupingColumns (service: DashboardsService) { const { groupColumn, subGroupColumn } = service.extractGroupingColumns( this.userSavedReportColumns, this.allColumns ); expect(groupColumn.columnName).to.be.equal('grantProgram.name'); expect(subGroupColumn.columnName).to.be.equal('application.applicationStatus'); } @TestCase('should be able to map type to api chart type') mapTypeToApiChartType (service: DashboardsService) { const apiType = service.mapTypeToApiChartType('bar'); expect(apiType).to.be.equal(AdHocReportingAPI.ChartType.Bar); } @TestCase('should be able to get should include other') getShouldIncludeOther (service: DashboardsService) { let shouldInclude = service.getShouldIncludeOther( AdHocReportingAPI.ChartType.Bar ); expect(shouldInclude).to.be.equal(true); shouldInclude = service.getShouldIncludeOther( AdHocReportingAPI.ChartType.Line ); expect(shouldInclude).to.be.equal(false); } @TestCase('should be able to get supported booleans') getSupportedBooleans (service: DashboardsService) { let supported = service['getSupportedBooleansByWidgetType']( 'bar' ); expect(supported.groupingSupported).to.be.true; expect(supported.legendSupported).to.be.true; expect(supported.sortingSupported).to.be.true; expect(supported.drilldownSupported).to.be.true; expect(supported.secondaryGroupingSupported).to.be.true; supported = service['getSupportedBooleansByWidgetType']( 'table' ); expect(supported.groupingSupported).to.be.false; expect(supported.legendSupported).to.be.false; expect(supported.sortingSupported).to.be.false; expect(supported.drilldownSupported).to.be.false; expect(supported.secondaryGroupingSupported).to.be.false; supported = service['getSupportedBooleansByWidgetType']( 'pie' ); expect(supported.groupingSupported).to.be.true; expect(supported.legendSupported).to.be.true; expect(supported.sortingSupported).to.be.false; expect(supported.drilldownSupported).to.be.true; expect(supported.secondaryGroupingSupported).to.be.false; } @TestCase('should be able to get simple default widget filters') getSimpleDefaultFilterColumn (service: DashboardsService) { let defaultFilter = service.getSimpleDefaultFilterColumn( 'application' ); expect(defaultFilter.column).to.be.equal('application.submittedDate'); defaultFilter = service.getSimpleDefaultFilterColumn( 'award' ); expect(defaultFilter.column).to.be.equal('award.awardDate'); defaultFilter = service.getSimpleDefaultFilterColumn( 'payment' ); expect(defaultFilter.column).to.be.equal('payment.scheduledDate'); } @TestCase('should be able to add other aggregate') addOtherAggregate (service: DashboardsService) { const chartDataResult = [{ label: '', subLabel: '', subValue: 1, xValue: '', yValue: 2, count: 0 }]; const result = service.addOtherAggregate( chartDataResult, 5, 0 ); expect(result[1].yValue).to.be.equal(5); } @TestCase('should be able to fetch and adapt widget detail') async getWidgetDetail (service: DashboardsService) { const response = await service.getWidgetDetail(this.widgetId); expect(response.aggregateColumn).to.be.equal('application.id'); expect(response.maxGroups).to.be.equal(10); expect(response.drilldownColumns.length).to.be.equal(1); } @TestCase('should be able to get sorted column list') getSortedColumnList (service: DashboardsService) { const sorted = service.getSortedColumnList( this.mappedChartData, 'application.id', 'application.id', false, 'bar' ); expect(sorted.length).to.be.equal(3); expect(sorted[0].recordSum).to.be.equal(2); expect(sorted[1].recordSum).to.be.equal(1); } @TestCase('should be able to get group column definition') getGroupColumnDef (service: DashboardsService) { const groupColumn = service.getGroupColumnDef( this.formFieldColumn1, this.allColumns ); expect(groupColumn).to.not.be.undefined; expect(groupColumn.column).to.be.equal(this.refFieldTextKey); expect(groupColumn.parentBucket).to.be.equal(`category.${this.categoryId}`); } @TestCase('should be able to enforce max rows') enforceMaxRows (service: DashboardsService) { let results: APIResultData = { records: [], recordCount: 0 }; service.enforceMaxRows(10, results); expect(results.recordCount).to.be.equal(0); results = { records: [1, 2], recordCount: 2 }; service.enforceMaxRows(1, results); expect(results.recordCount).to.be.equal(1); } @TestCase('should be able to check if definition exists') checkIfDefinitionExists (service: DashboardsService) { let exists = service.checkIfDefinitionExists( this.allColumns, 'grantProgram.name' ); expect(exists).to.be.true; exists = service.checkIfDefinitionExists( this.allColumns, 'grantProgram.id' ); expect(exists).to.be.false; } @TestCase('should call the locked endpoint if dashboard is not owner') async checkCallsLockedMoveEndpoint (service: DashboardsService) { let calledLock = false; service['dashboardsResources'].updateDashboardTab = (() => {}) as any; service['dashboardsResources'].moveLockedDashboard = (() => calledLock = true) as any; service['resetMyDashboards'] = (() => {}) as any; await service.handleUpdateTabOrder(0, '', 0, false); expect(calledLock).to.be.true; } @TestCase('should call the owner endpoint if dashboard is owner') async checkCallsOwnerMoveEndpoint (service: DashboardsService) { let calledOwner = false; service['dashboardsResources'].moveLockedDashboard = (() => {}) as any; service['dashboardsResources'].updateDashboardTab = (() => calledOwner = true) as any; service['resetMyDashboards'] = (() => {}) as any; await service.handleUpdateTabOrder(0, '', 0, true); expect(calledOwner).to.be.true; } @TestCase('should map shared users to audience members') async checkShouldMapSharedUserToAudienceMember (service: DashboardsService) { const ownerUserId = 1; const viewOnlyUserId = 2; const users: GCDashboards.DashboardSharedUserInfo[] = [{ email: 'owner', firstName: 'owner', isDashboardOwner: true, lastName: 'owner', userId: ownerUserId }, { email: 'view-only', firstName: 'view', isDashboardOwner: false, lastName: 'only', userId: viewOnlyUserId }]; service['dashboardsResources'].getDashboardSharedUsers = async (d) => users; const audienceMembers = await service.fetchDashboardSharedUsers(0); const owner = audienceMembers.find(m => m.id === ownerUserId); const viewOnly = audienceMembers.find(m => m.id === viewOnlyUserId); expect(owner.canManage).to.be.true; expect(viewOnly.canManage).to.be.false; } @TestCase('should set correct viewOnly dashboards') async testShouldSetCorrectViewOnlyDashboards (service: DashboardsService) { await service.resetMyDashboards(); expect(service.allDashboards.length).to.equal(2); expect(service.visibleDashboards.length).to.equal(1); const hasVisible = service.visibleDashboards.some((d) => { return d.dashboardId === this.visibleDashboardId; }); expect(hasVisible).to.be.true; } @TestCase('should be able to determine share removals') testShouldDetermineShareRemovals (service: DashboardsService) { const newMembers: AudienceMember[] = []; const oldMembers: AudienceMember[] = [{ canManage: true, email: '', id: 1, name: '' }]; const result = service.determineShareChanges(oldMembers, newMembers); expect(result.changes.length).to.equal(0); expect(result.removals.length).to.equal(1); } @TestCase('should be able to determine share additions') testShouldDetermineShareAdditions (service: DashboardsService) { const newMembers: AudienceMember[] = [{ canManage: true, email: '', id: 1, name: '' }]; const oldMembers: AudienceMember[] = []; const result = service.determineShareChanges(oldMembers, newMembers); expect(result.changes.length).to.equal(1); expect(result.removals.length).to.equal(0); } @TestCase('should be able to determine share changes') testShouldDetermineShareChanges (service: DashboardsService) { const newMembers: AudienceMember[] = [{ canManage: true, email: '', id: 1, name: '' }]; const oldMembers: AudienceMember[] = [{ canManage: false, email: '', id: 1, name: '' }]; const result = service.determineShareChanges(oldMembers, newMembers); expect(result.changes.length).to.equal(1); expect(result.removals.length).to.equal(1); } @TestCase('should be able to determine no share changes') testShouldDetermineNoShareChanges (service: DashboardsService) { const newMembers: AudienceMember[] = [{ canManage: true, email: '', id: 1, name: '' }]; const oldMembers: AudienceMember[] = [{ canManage: true, email: '', id: 1, name: '' }]; const result = service.determineShareChanges(oldMembers, newMembers); expect(result.changes.length).to.equal(0); expect(result.removals.length).to.equal(0); } @TestCase('should be able to return the correct tooltip string for pie charts') testReturningCorrectTooltipStringForPie (service: DashboardsService) { const pieSumTooltip = service.formatTooltipValue( this.formatTooltipPayloadPieSum ); const expectedPieSum = '$12600 (96) (5.77%)'; expect(pieSumTooltip).to.be.equal(expectedPieSum); const pieCountTooltip = service.formatTooltipValue( this.formatTooltipPayloadPieCount ); const expectedPieCount = '102 (14.78%)'; expect(pieCountTooltip).to.be.equal(expectedPieCount); } @TestCase('should be able to return the correct tooltip string for bar charts') testReturningCorrectTooltipStringForBar (service: DashboardsService) { const barCountTooltip = service.formatTooltipValue( this.formatTooltipPayloadBarCount ); const expectedBarCount = '8'; expect(barCountTooltip).to.be.equal(expectedBarCount); const barSumTooltip = service.formatTooltipValue( this.formatTooltipPayloadBarSum ); const expectedBarSum = '$2977 (5)'; expect(barSumTooltip).to.be.equal(expectedBarSum); } @TestCase('should be able to get type options by object') getTypeSelectOptionsByObject (service: DashboardsService) { let options = service.getTypeSelectOptionsByObject('budgets'); expect(options.length).to.be.equal(3); expect(options[0].value).to.be.equal('pie'); expect(options[1].value).to.be.equal('stat'); expect(options[2].value).to.be.equal('table'); options = service.getTypeSelectOptionsByObject('application'); const allOptions = service.typeSelectOptions; expect(options.length).to.be.equal(allOptions.length); } @TestCase('should be able to set widget edit map') setWidgetEditMap (service: DashboardsService) { service.setWidgetEditMap(this.widgetId, this.widget); expect(service.widgetEditMap[this.widgetId]).to.deep.equal(this.widget); } @TestCase('should be able to set last refresh') setLastRefreshDate (service: DashboardsService) { const lastRefreshDate = new Date().toString(); service.setLastRefreshDate(this.widgetId, lastRefreshDate); expect(service.lastRefreshMap[this.widgetId]).to.deep.equal(lastRefreshDate); } @TestCase('should be able to reset edit widget map') resetWidgetEditMap (service: DashboardsService) { service.resetWidgetEditMap(); const isEmptyMap = Object.keys(service.widgetEditMap).length === 0; expect(isEmptyMap).to.be.true; } @TestCase('should be able to set editing') setEditing (service: DashboardsService) { service.setEditing(true, 1); expect(service.editing).to.be.true; expect(service.editingDashId).to.be.equal(1); } @TestCase('should be able to set prompting user for unsaved changes') setPromptingForUnsavedChanges (service: DashboardsService) { service.setPromptingForUnsavedChanges(true); expect(service.promptingForUnsavedChanges).to.be.true; } @TestCase('should be able to cancel edit mode') cancelEditMode (service: DashboardsService) { service.cancelEditMode(); expect(service.editing).to.be.false; const isEmptyMap = Object.keys(service.widgetEditMap).length === 0; expect(isEmptyMap).to.be.true; } @TestCase('should be able to reset all widgets') async resetAllWidgets (service: DashboardsService) { service.setAllWidgets([]); await service.resetAllWidgets(); expect(service.allWidgets.length).to.be.equal(this.allWidgets.length); const widget = service.allWidgets[0]; expect(widget.id).to.be.equal(this.allWidgets[0].id); } @TestCase('should be able to fetch widgets') async fetchWidgets (service: DashboardsService) { await service.fetchWidgets(); expect(service.allWidgets.length).to.be.equal(this.allWidgets.length); const widget = service.allWidgets[0]; expect(widget.id).to.be.equal(this.allWidgets[0].id); } @TestCase('should be able to adapt to simple widget') adaptToSimpleWidget (service: DashboardsService) { const adapted = service.adaptToSimpleWidget(this.allWidgets[0]); expect(adapted.object).to.be.equal('application'); expect(adapted.type).to.be.equal('bar'); } @TestCase('should be able to get default widget filters') getDefaultWidgetFilters (service: DashboardsService) { const buckets = service['adHocService'].getBuckets( 'application', [], [], AdHocReportingUI.Usage.DASHBOARDS ); const filters = service.getDefaultWidgetFilters('application', buckets); const filter = filters.filters[0]; expect(filter.column.columnName).to.be.equal('application.submittedDate'); expect(filters.useAnd).to.be.equal(SwitchState.Toggled); } @TestCase('should be able to adapt report to chart') async adaptReportToChart (service: DashboardsService) { const adapted = await service.adaptReportToChart(this.widget); const expectedGroupColumn = 'grantProgram.name'; const expectedSubGroupColumn = 'application.applicationStatus'; const expectedAggregateColumn = 'application.id'; const expectedAggregateType = AdHocReportingAPI.ChartAggregateType.Count; expect(adapted.groupColumn).to.be.equal(expectedGroupColumn); expect(adapted.subGroupColumn).to.be.equal(expectedSubGroupColumn); expect(adapted.aggregateColumn).to.be.equal(expectedAggregateColumn); expect(adapted.aggregationType).to.be.equal(expectedAggregateType); } @TestCase('should be able to map user saved col to drilldown cols') mapUserSavedColToDrilldownCols (service: DashboardsService) { const drilldownCols = service.mapUserSavedColToDrilldownCols( this.userSavedReportColumns, this.allColumns ); const expectedDrilldownCols = this.userSavedReportColumns.filter((col) => { return !!col.sortPriority; }); expect(drilldownCols.length).to.be.equal(expectedDrilldownCols.length); const expectedDrilldownColNames = expectedDrilldownCols.map((col) => { return col.columnName; }); const foundAll = expectedDrilldownColNames.every((name) => { return drilldownCols.find((col) => { const colName = `${col.definition.parentBucket}.${col.definition.column}`; return name === colName; }); }); expect(foundAll).to.be.true; } @TestCase('should be able to map user saved col to column filter rows') mapUserSavedColToColumnFilterRows (service: DashboardsService) { const filterRows = service.mapUserSavedColToColumnFilterRows( this.userSavedReportColumns, this.allColumns ); const expectedFilterRowNames = this.userSavedReportColumns.filter((col) => { return col.userSavedFilterColumns.length > 0; }).map((col) => { return col.columnName; }); const foundAll = expectedFilterRowNames.every((name) => { return filterRows.find((col) => { const colName = col.column.columnName; return name === colName; }); }); expect(foundAll).to.be.true; } @TestCase('should be able to get column defs') async getColumnDefs (service: DashboardsService) { const columnDefs = service.getColumnDefs( AdHocReportingAPI.AdHocReportModelType.Application, [this.formId], this.userSavedReportColumns ); expect(columnDefs.length).to.be.equal(this.userSavedReportColumns.length); const hasTextRefField = columnDefs.some((col) => { return col.definition.column === this.refFieldTextKey; }); const hasNumberRefField = columnDefs.some((col) => { return col.definition.column === this.refFieldNumberKey; }); expect(hasTextRefField).to.be.true; expect(hasNumberRefField).to.be.true; } @TestCase('should be able to map api chart to type') mapApiChartTypeToType (service: DashboardsService) { let type = service.mapApiChartTypeToType( AdHocReportingAPI.ChartType.Stat ); expect(type).to.be.equal('stat'); type = service.mapApiChartTypeToType( AdHocReportingAPI.ChartType.Bar ); expect(type).to.be.equal('bar'); type = service.mapApiChartTypeToType( AdHocReportingAPI.ChartType.Table ); expect(type).to.be.equal('table'); } @TestCase('should be able to determine colors') determineColors (service: DashboardsService) { const colors = service.determineColors(6); const expectedColors = [ '#0000ff', '#00ccff', '#00ff66', '#1ae61a', '#4db34d', '#808080' ]; const hasAllColors = expectedColors.every((color) => { return colors.find((_color) => { return color === _color; }); }); expect(hasAllColors).to.be.true; } @TestCase('should be able to get chart data') async getChartData (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const chartData = await service.getChartData( config, false, 1 ); const result = chartData.data[0]; const expectedValue = ApplicationStatuses.Approved; expect(result.value).to.be.equal(expectedValue); } @TestCase('should be able to map result to chart data') async mapResultToChartData (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const chartData = service.mapResultToChartData( config, this.chartData ); const result = chartData.data[0]; const expectedValue = ApplicationStatuses.Approved; expect(result.value).to.be.equal(expectedValue); } @TestCase('should be able to get grouped chart data') async getGroupedChartData (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const yValue = 12; const count = 13; const mappedChartData: GCDashboards.ChartDataResult[] = [{ label: '', subLabel: '', subValue: '', xValue: 26, yValue, count }]; const colors = service.determineColors(mappedChartData.length); const groupedChartData = service.getGroupedChartData( config, mappedChartData, colors ); const result = groupedChartData.data[0]; expect(result.data).to.deep.equal([yValue]); expect(result.counts).to.deep.equal([count]); } @TestCase('should be able to get sub grouped chart data') async getSubGroupedChartData (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const yValue = 12; const count = 13; const subValue = 34; const mappedChartData: GCDashboards.ChartDataResult[] = [{ label: 'Label', subLabel: 'Sub Label', subValue, xValue: 26, yValue, count }]; const subGroupedChartData = service.getSubGroupedChartData( mappedChartData, config ); const result = subGroupedChartData.data[0]; expect(result.value).to.be.equal(subValue); } @TestCase('should be able to handle widget position change') handleWidgetPositionChange (service: DashboardsService) { const gridsterItem = { id: this.widget.id, x: 1, y: 1, rows: 1, cols: 1 }; service.setEditing(true, 1); service.handleWidgetPositionChange( gridsterItem, [this.widget] ); const updatedWidget = service.widgetEditMap[1]; const updatedConfig = JSON.parse(updatedWidget.chartConfig); expect(updatedConfig.x).to.be.equal(gridsterItem.x); expect(updatedConfig.y).to.be.equal(gridsterItem.y); expect(updatedConfig.width).to.be.equal(gridsterItem.cols); expect(updatedConfig.height).to.be.equal(gridsterItem.rows); } @TestCase('should be able to get form ids') getFormIds (service: DashboardsService) { const widgetConfig = { customForms: [1, 2], primaryFormId: 4 } as GCDashboards.WidgetConfig; const formIds = service.getFormIds(widgetConfig); const hasCustomFormIds = formIds.includes(1) && formIds.includes(2); const hasPrimaryFormId = formIds.includes(4); expect(hasCustomFormIds).to.be.true; expect(hasPrimaryFormId).to.be.true; } @TestCase('should be able to get sort attrs from pagination') getSortAttrsFromPagination (service: DashboardsService) { const sortColumn = 'application.id'; const options: PaginationOptions = { rowsPerPage: 5, pageNumber: 1, sortColumns: [{ columnName: sortColumn, sortAscending: true }], filterColumns: [], orFilterColumns: [], retrieveTotalRecordCount: true, returnAll: false }; const sortAttrs = service.getSortAttrsFromPagination(options); expect(sortAttrs.sortAscending).to.be.true; expect(sortAttrs.sortColumn).to.be.equal(sortColumn); } @TestCase('should be able to get header from click event') getHeaderFromClickEvent (service: DashboardsService) { const clickEvent: Dashboards.ChartClickEvent = { chart: { subGroupColumn: 'Sub Header' } as GCDashboards.WidgetConfig, values: [], groupDisplay: 'Header', groupValue: '', subGroupDisplay: 'Sub Header', subGroupValue: '' }; const header = service.getHeaderFromClickEvent(clickEvent); expect(header).to.be.equal(`${clickEvent.groupDisplay} - ${clickEvent.chart.subGroupColumn}`); } @TestCase('should be able to map GC chart to common') async mapGCChartToCommon (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const buckets = service['adHocService'].getBuckets( 'application', [], [], AdHocReportingUI.Usage.DASHBOARDS ); const chartData = await service.getChartData( config, false, 1 ); const mapped = service.mapGCChartToCommon( config, chartData, buckets ); expect(mapped.id).to.be.equal(config.id); expect(mapped.object).to.be.equal('application'); expect(mapped.chartType).to.be.equal('bar'); expect(config.aggregationType).to.be.equal(config.aggregationType); } @TestCase('should be able to format y axis value') formatYAxisValue (service: DashboardsService) { let isCurrency = true; let formatted = service.formatYAxisValue(false, isCurrency, 500.27); expect(formatted).to.be.equal('$500.27'); isCurrency = false; formatted = service.formatYAxisValue(false, isCurrency, 500.27); expect(formatted).to.be.equal('500.27'); } @TestCase('should be able to map user saved report columns for aggregation') async mapToUserSavedReportColumnsForAggregation (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const columns = service.mapToUserSavedReportColumnsForAggregation( config ); const applicationColumn = columns.find((col) => { return col.columnName === 'application.id'; }); const programColumn = columns.find((col) => { return col.columnName === 'grantProgram.name'; }); const statusColumn = columns.find((col) => { return col.columnName === 'application.applicationStatus'; }); const applicationIsAggregate = applicationColumn.isChartAggregate; const programIsGrouping = programColumn.isChartGroupingColumn; const statusIsSubGrouping = statusColumn.isChartSubGroupingColumn; expect(applicationIsAggregate).to.be.true; expect(programIsGrouping).to.be.true; expect(statusIsSubGrouping).to.be.true; } @TestCase('should be able to map user saved report columns for tables') async mapToUserSavedReportColumnsForTables (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const columns = service.mapToUserSavedReportColumnsForTables( config ); const hasFilterColumn = columns.some((col) => { return col.columnName === 'grantProgram.name'; }); const hasDrilldownColumn = columns.some((col) => { return col.columnName === 'application.applicationStatus'; }); expect(columns.length).to.be.equal(2); expect(hasFilterColumn).to.be.true; expect(hasDrilldownColumn).to.be.true; } @TestCase('should be able to map to user saved filter col') mapToUserSavedFilterCol (service: DashboardsService) { const filter: FilterColumn = { columnName: 'application.id', filters: [{ filterType: 'eq', filterValue: 134 }] }; const mapped = service.mapToUserSavedFilterCol(filter); const isMapped = mapped.some((_filter) => { return _filter.filterType === 'eq' && _filter.filterValue === 134; }); expect(isMapped).to.be.true; } @TestCase('should be able to set advanced filter for record summary') async setAdvancedFilterForRecordSummary (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const result = service.setAdvancedFilterForRecordSummary(config); const filter = result[0]; const hasIdFilter = filter.columnName === 'application.id'; const hasCorrectFilterType = filter.filterType === 'eq'; expect(result.length).to.be.equal(1); expect(hasIdFilter).to.be.true; expect(hasCorrectFilterType).to.be.true; } @TestCase('should be able to map to user saved report cols for summary') async mapToUserSavedReportColumnsForSummary (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const idIndex = 0; const userSavedReportCols = [ ...this.userSavedReportColumns.slice(0, idIndex), ...this.userSavedReportColumns.slice(idIndex + 1) ]; const result = service.mapToUserSavedReportColumnsForSummary( config, userSavedReportCols ); const hasIdCol = result.some((col) => { return col.columnName === 'application.id'; }); expect(hasIdCol).to.be.true; } @TestCase('should be able to adapt chart config for api') async adaptChartConfigForAPI (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const adapted = service.adaptChartConfigForAPI(config); expect(adapted.reportType).to.be.equal(AdHocReportingAPI.AdHocReportType.Chart); expect(adapted.reportModelType).to.be.equal(AdHocReportingAPI.AdHocReportModelType.Application); const chartConfigIsString = typeof adapted.chartConfig === 'string'; expect(chartConfigIsString).to.be.true; } @TestCase('should be able to convert ad hoc column to filter column') adHocColumnToFilterColumn (service: DashboardsService) { const idColumn: AdHocReportingUI.ColumnDefinition = { i18nKey: '', parentBucketName: 'Application', parentBucket: 'application', display: 'Application ID', column: 'id', type: 'text' }; const result = service.adHocColumnToFilterColumn([idColumn]); const adapted = result[0]; const expectedLabel = `${idColumn.display} (${idColumn.parentBucketName})`; expect(adapted.label).to.be.equal(expectedLabel); } @TestCase('should be able to get dashboard detail') async getDashboardDetail (service: DashboardsService) { await service.getDashboardDetail( 1 ); const detail = service.dashboardDetails[1]; expect(detail.name).to.be.equal('My Dash'); const widget = detail.widgets[0]; expect(widget.id).to.be.equal(this.widget.id); } @TestCase('should be able to set my dashboards') async setMyDashboards (service: DashboardsService) { await service.setMyDashboards(); const myDashboards = service.allDashboards; const hasFirstDash = myDashboards.some((dash) => { return dash.dashboardId === this.dashboardTabs[0].dashboardId; }); const hasSecondDash = myDashboards.some((dash) => { return dash.dashboardId === this.dashboardTabs[1].dashboardId; }); expect(myDashboards.length).to.be.equal(2); expect(hasFirstDash).to.be.true; expect(hasSecondDash).to.be.true; } @TestCase('should be able to tell if we should format as currency') shouldFormatAsCurrency (service: DashboardsService) { const result = service.shouldFormatAsCurrency( 'application.amountRequested', service['adHocService'].getBuckets( 'application', [], [], AdHocReportingUI.Usage.DASHBOARDS ) ); expect(result).to.be.true; } @TestCase('should be able to align pagination options for drilldown') async alignPaginationOptionsForDrilldown (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const paginationOptions: PaginationOptions = { rowsPerPage: 10, pageNumber: 1, sortColumns: [], filterColumns: [], retrieveTotalRecordCount: true, returnAll: false }; const programIdToFilterOut = 4781; const drilldownValue: Dashboards.ChartClickEvent = { chart: { subGroupColumn: 'Sub Header', groupColumn: 'grantProgram.name' } as GCDashboards.WidgetConfig, values: [{ display: 'Other', value: OTHER_OPTION }, { display: 'Program 1', value: programIdToFilterOut }], groupDisplay: 'Header', groupValue: OTHER_OPTION, subGroupDisplay: 'Sub Header', subGroupValue: '' }; const result = service.alignPaginationOptionsForDrilldown( config, paginationOptions, drilldownValue ); const hasFilteredOutProgram1 = result.filterColumns.some((col) => { const isProgramFilter = col.columnName === 'grantProgram.name'; if (isProgramFilter) { const found = col.filters.find((filter) => { return filter.filterType === FilterModalTypes.notEqual && +filter.filterValue === programIdToFilterOut; }); return !!found; } return false; }); expect(hasFilteredOutProgram1).to.be.true; } @TestCase('should be able to align report columns for drilldown') async alignReportColumnsForDrilldown (service: DashboardsService) { const config = await service.adaptReportToChart(this.widget); const programIdToFilterOut = 4781; const drilldownValue: Dashboards.ChartClickEvent = { chart: { subGroupColumn: 'Sub Header', groupColumn: 'grantProgram.name' } as GCDashboards.WidgetConfig, values: [{ display: 'Other', value: OTHER_OPTION }, { display: 'Program 1', value: programIdToFilterOut }], groupDisplay: 'Header', groupValue: OTHER_OPTION, subGroupDisplay: 'Sub Header', subGroupValue: '' }; const updatedColumns = service.alignReportColumnsForDrilldown( config, this.userSavedReportColumns, drilldownValue ); const programColumn = updatedColumns.find((col) => { return col.columnName === 'grantProgram.name'; }); const hasFilteredOutProgram1 = programColumn.userSavedFilterColumns.some((filter) => { return filter.filterType === FilterModalTypes.notEqual && +filter.filterValue === programIdToFilterOut; }); expect(hasFilteredOutProgram1).to.be.true; } @TestCase('should be able to set my dashboards with standard product dashboards ') async testSettingMyDashboardsWithStandardProductDashboards ( service: DashboardsService ) { await service.setMyDashboards(); const standardProductDBs = service.dashboardManagerRows.filter((dashboard) => dashboard.isStandardDashboardPublished); expect(standardProductDBs).to.deep.equal(this.standardProductDashboardList); } @TestCase('should be able to fetch preview data for widgets') async testFetchingPreviewDataForWidgets ( service: DashboardsService ) { service['mapResultToChartData'] = () => { return null as GCDashboards.ChartDataReturn; }; service['mapGCChartToCommon'] = () => { return null as GCDashboards.Widget; }; service['mapToChartWidget'] = async () => { return { id: null } as GCDashboards.Widget; }; const mapToChartWidgetSpy = spy.on( service, 'mapToChartWidget' ); await service.fetchWidgetDetailForDashboard( this.widget, this.visibleDashboardId, true, false ); expect(mapToChartWidgetSpy).to.have.been.called(); } @TestCase('should be able to map chart widgets for preview') async testMappingChartWidgetsForPreview ( service: DashboardsService ) { service['returnWidgetPreview'] = async () => { return null as GCDashboards.ChartDataReturn; }; service['mapGCChartToCommon'] = () => { return null as GCDashboards.Widget; }; const returnWidgetPreviewSpy = spy.on( service, 'returnWidgetPreview' ); const widgetConfig = { customForms: [1, 2], primaryFormId: 4, drilldownColumns: [{ }], advancedFilters: [{ filters: [] }] } as GCDashboards.WidgetConfig; const buckets: AdHocReportingUI.ColumnBucket[] = [{ i18nKey: 'string', display: '', property: 'application', columns: [{ definition: { column: 'approvedDate', type: 'text', i18nKey: 'common:textApprovedDate', format: 'value', display: 'approvedDate' }, sortType: AdHocReportingAPI.SortTypes.NoSort, filterColumn: { columnName: '', filters: [] }, visibleInReport: false, columnNameOverride: '' }], allColumns: [] }]; await service.mapToChartWidget( widgetConfig, buckets, true, false ); expect(returnWidgetPreviewSpy).to.have.been.called(); } }