import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, Validators } from '@angular/forms'; import { SpinnerService } from '@core/services/spinner.service'; import { AdHocReportingAPI } from '@core/typings/api/ad-hoc-reporting.typing'; import { AdHocReportingUI } from '@core/typings/ui/ad-hoc-reporting.typing'; import { FormsService } from '@features/configure-forms/services/forms/forms.service'; import { AdHocReportingDefinitions, RootObjectNames } from '@features/reporting/services/ad-hoc-reporting-definitions.service'; import { AdHocReportingMappingService } from '@features/reporting/services/ad-hoc-reporting-mapping.service'; import { AdHocReportingService } from '@features/reporting/services/ad-hoc-reporting.service'; import { AdvancedFilterGroup, APISortColumn, ArrayHelpersService, Column, FilterBuilderService, SimpleStringMap, SwitchState, Tab, TypeaheadSelectOption, TypeSafeFormBuilder, TypeSafeFormGroup } from '@yourcause/common'; import { AnalyticsService, EventType } from '@yourcause/common/analytics'; import { I18nService } from '@yourcause/common/i18n'; import { LogService } from '@yourcause/common/logging'; import { YCModalComponent } from '@yourcause/common/modals'; import { Subscription } from 'rxjs'; import { DashboardsService } from '../dashboards.service'; import { Dashboards, DEFAULT_AGGREGATE_COLUMN, DEFAULT_AGGREGATION_TYPE, DEFAULT_CHART_TYPE, DEFAULT_LEGEND_LOCATION, DEFAULT_MAX_GROUPS, DEFAULT_OBJECT, DEFAULT_ROWS_PER_PAGE, GCDashboards, ManageWidgetTabs } from '../dashboards.typing'; @Component({ selector: 'yc-manage-widget-modal', templateUrl: './manage-widget-modal.component.html', styleUrls: ['./manage-widget-modal.component.scss'] }) export class ManageWidgetModalComponent extends YCModalComponent implements OnInit, OnDestroy { @Input() existingWidget: GCDashboards.WidgetConfig; @Input() dashboardId: number; @Input() isUpdate = false; Infinity = Infinity; ManageWidgetTabs = ManageWidgetTabs; previewWidget: GCDashboards.Widget; tabs: Tab[] = []; legendLocationOptions = this.dashboardsService.legendLocationOptions; activeTab: ManageWidgetTabs = ManageWidgetTabs.DETAILS; maxDataLabel = ''; formGroup: TypeSafeFormGroup; groupingSupported: boolean; secondaryGroupingSupported: boolean; aggregateSupported: boolean; sortingSupported: boolean; drilldownSupported: boolean; legendSupported: boolean; axisLabelSupported: boolean; isSummaryRecordType: boolean; hasRecordOptions: boolean; dataTabSupported: boolean; filteringSupported: boolean; summaryRecordLabel: string; objectOptions = this.dashboardsService.getSourceSelectOptions(); typeOptions = this.dashboardsService.typeSelectOptions; aggregationOptions = this.dashboardsService.aggregationOptions; sortDirectionOptions = this.dashboardsService.sortDirectionOptions; simpleDrilldownColumns: GCDashboards.SimpleColumn[] = []; drilldownColumns: AdHocReportingUI.ColumnImplementation[] = []; availableFilterColumns: Column[] = []; filterBuilderValid = true; drilldownValid = true; tableColumnsValid: boolean; summaryRecordOptions: TypeaheadSelectOption[] = []; summaryColumnOptions: TypeaheadSelectOption[] = []; allColumnOptions: TypeaheadSelectOption[] = []; allColumnOptionsHelperMap: SimpleStringMap = {}; groupColumnOptions: TypeaheadSelectOption[] = []; sortColumnOptions: TypeaheadSelectOption[] = []; subGroupColumnOptions: TypeaheadSelectOption[] = []; aggregateColumnOptions: TypeaheadSelectOption[] = []; currentBuckets: AdHocReportingUI.ColumnBucket[]; requiredError = { required: { i18nKey: 'common:textThisInputIsRequired', defaultValue: 'This input is required' } }; greaterThanZeroError = { greaterThanZero: { i18nKey: 'common:textMustBeGreaterThanZero', defaultValue: 'Must be between greater than zero' } }; formIds: number[] = []; isValid = false; ready = false; sub = new Subscription(); previewWidgetError = ''; defaultFilterAdded: AdvancedFilterGroup; gmFormsWithSingleResponseFields: number[] = []; invalidColumnHeadersError: string; filtersUsingAnd: SwitchState; initialGroups: AdvancedFilterGroup[] = []; currentGroups: AdvancedFilterGroup[] = []; constructor ( private logger: LogService, private formBuilder: TypeSafeFormBuilder, private adHocService: AdHocReportingService, private dashboardsService: DashboardsService, private i18n: I18nService, private spinnerService: SpinnerService, private arrayHelper: ArrayHelpersService, private adHocMapping: AdHocReportingMappingService, private filterBuilderService: FilterBuilderService, private formService: FormsService, private adHocDefinitions: AdHocReportingDefinitions, private analyticsService: AnalyticsService ) { super(); } async ngOnInit () { this.spinnerService.startSpinner(); const widget: GCDashboards.WidgetConfig = this.existingWidget || { name: '', description: '', type: DEFAULT_CHART_TYPE, object: DEFAULT_OBJECT, aggregateColumn: DEFAULT_AGGREGATE_COLUMN, aggregationType: DEFAULT_AGGREGATION_TYPE, groupColumn: '', maxGroups: DEFAULT_MAX_GROUPS, subGroupColumn: '', filters: [], legendLocation: DEFAULT_LEGEND_LOCATION, sortColumn: '', sortAscending: false, xAxisLabel: '', yAxisLabel: '', customForms: [], primaryFormId: null, rowsPerPage: DEFAULT_ROWS_PER_PAGE, hasMaxRows: false, maxRows: null, advancedFilters: [], useAnd: SwitchState.Toggled, summaryRecordId: null }; this.formGroup = this.formBuilder.group({ name: [widget.name, Validators.required], description: [widget.description], type: [widget.type, Validators.required], object: [widget.object, Validators.required], aggregateColumn: [widget.aggregateColumn], aggregationType: [widget.aggregationType], groupColumn: [widget.groupColumn], maxGroups: [widget.maxGroups], subGroupColumn: [widget.subGroupColumn], filters: null, legendLocation: widget.legendLocation, sortColumn: widget.sortColumn, sortAscending: widget.sortAscending, xAxisLabel: widget.xAxisLabel, yAxisLabel: widget.yAxisLabel, primaryFormId: widget.primaryFormId, customForms: [widget.customForms], rowsPerPage: widget.rowsPerPage, hasMaxRows: !!widget.maxRows, maxRows: widget.maxRows, advancedFilters: null, useAnd: null, summaryRecordId: widget.summaryRecordId }, { validator: [ this.aggregateRequiredValidator(), this.groupingRequiredValidator(), this.maxGroupsValidator(), this.sortColumnValidator(), this.customFormValidator(), this.tableRowOptionsValidator(), this.summaryRecordRequiredValidator() ] }); this.sub.add(this.formGroup.statusChanges.subscribe(() => { this.setValidity(); })); const [ gmFormsWithSingleResponseFields ] = await Promise.all([ this.formService.getGmFormsWithSingleResponseFields(), this.fetchFormComponents() ]); this.gmFormsWithSingleResponseFields = gmFormsWithSingleResponseFields; this.objectChanged(this.formGroup.value.object, true); this.typeChanged(this.formGroup.value.type, true); this.setInitialColumns(); this.setFormIds(); this.ready = true; if (this.isValid) { await this.setPreviewWidget(); } this.spinnerService.stopSpinner(); } setFormIds () { const formIds = this.dashboardsService.getFormIds(this.formGroup.value); this.formIds = formIds; return formIds; } async fetchFormComponents () { const formIds = this.setFormIds(); await this.adHocService.resolveFormInfo( formIds, this.adHocDefinitions[this.formGroup.value.object] ); } getDefaultWidgetFilters (object: RootObjectNames) { const buckets = this.currentBuckets || this.adHocService.getBuckets( object, [], [], AdHocReportingUI.Usage.DASHBOARDS ); return this.dashboardsService.getDefaultWidgetFilters( object, buckets ); } async customFormsChanged () { await this.fetchFormComponents(); this.updateColumnOptions(this.formGroup.value.object); // Ensure column arrays are still valid after form change this.initialGroups = this.currentGroups.map((group) => { group.filters = group.filters.filter(col => { return this.allColumnOptionsHelperMap[col.column?.prop]; }); return group; }).filter(group => !!group && group.filters.length); this.currentGroups = this.initialGroups; this.simpleDrilldownColumns = this.simpleDrilldownColumns.filter((col) => { return this.allColumnOptionsHelperMap[col.column]; }); this.drilldownColumns = this.adHocMapping.mapSimpleColumnToColumnImplementation( this.simpleDrilldownColumns, this.currentBuckets ); const groupCol = this.formGroup.value.groupColumn; const subGroupCol = this.formGroup.value.subGroupColumn; if (groupCol && !this.allColumnOptionsHelperMap[groupCol]) { this.formGroup.get('groupColumn').setValue(null); } if (subGroupCol && !this.allColumnOptionsHelperMap[subGroupCol]) { this.formGroup.get('subGroupColumn').setValue(null); } this.updateBadgeNumber(); this.realignOptions(); } setInitialColumns () { this.setInitialFilterColumns(); this.setInitialDrilldownColumns(); } setInitialFilterColumns () { let filters: AdvancedFilterGroup[] = []; if (!this.existingWidget) { filters = [this.getDefaultWidgetFilters(DEFAULT_OBJECT)]; this.defaultFilterAdded = filters[0]; this.filtersUsingAnd = SwitchState.Toggled; } else { filters = this.existingWidget.advancedFilters || []; this.filtersUsingAnd = (this.existingWidget.useAnd === SwitchState.Toggled) ? SwitchState.Toggled : SwitchState.Untoggled; } this.initialGroups = this.filterBuilderService.remapColumns( this.availableFilterColumns, filters, false ); this.currentGroups = this.initialGroups; this.updateBadgeNumber(); } setInitialDrilldownColumns () { if (this.existingWidget) { const type = this.existingWidget.type; const { drilldownSupported } = this.dashboardsService.getSupportedBooleans(type, this.formGroup.value.object); if (drilldownSupported || type === 'table') { this.simpleDrilldownColumns = this.mapToSimpleColumn( this.existingWidget.drilldownColumns ); this.drilldownColumns = this.existingWidget.drilldownColumns; } this.tableColumnsValid = true; this.setValidity(); } } setTypeOptions () { this.typeOptions = this.dashboardsService.getTypeSelectOptionsByObject( this.formGroup.value.object ); const currentType = this.formGroup.value.type; let currentTypeAllowed = false; this.typeOptions.forEach((option) => { if (currentType === option.value) { currentTypeAllowed = true; } }); if (!currentTypeAllowed) { this.formGroup.get('type').setValue( this.typeOptions[0].value ); } } async setPreviewWidget () { if (this.isValid) { const mapToWidgetPayload: GCDashboards.WidgetConfig = { ...this.formGroup.value, filters: [], drilldownColumns: this.drilldownColumns, advancedFilters: this.currentGroups || this.initialGroups, useAnd: this.filtersUsingAnd }; this.spinnerService.startSpinner(); try { this.previewWidget = await this.dashboardsService.mapToChartWidget( mapToWidgetPayload, this.currentBuckets, true, false ); this.previewWidgetError = ''; } catch (e) { this.logger.error(e); this.previewWidgetError = this.i18n.translate( 'DASHBOARD:textErrorFetchingWidgetName', { widgetName: this.formGroup.value.name }, 'There was an error displaying widget __widgetName__' ); } this.spinnerService.stopSpinner(); this.analyticsService.emitEvent({ eventName: 'Set preview widget', eventType: EventType.Click, extras: null }); } } mapToSimpleColumn (columns: AdHocReportingUI.ColumnImplementation[] = []) { return columns.map((column) => { return { column: `${column.definition.parentBucket}.${column.definition.column}`, columnNameOverride: column.columnNameOverride }; }); } hasMaxRowsChanged (hasMaxRows: boolean) { if (hasMaxRows) { this.formGroup.get('maxRows').setValue(10); } else { this.formGroup.get('maxRows').setValue(null); } } tableColumnsChanged (columns: GCDashboards.SimpleColumn[]) { this.simpleDrilldownColumns = columns; this.drilldownColumns = this.adHocMapping.mapSimpleColumnToColumnImplementation( columns, this.currentBuckets ); this.setValidity(); } tableSortColumnChanged (sortColumn: APISortColumn) { this.formGroup.get('sortColumn').setValue(sortColumn.columnName); this.formGroup.get('sortAscending').setValue(sortColumn.sortAscending); } groupsChanged (groups: AdvancedFilterGroup[] = []) { this.currentGroups = groups; if (this.defaultFilterAdded) { // check to see if the default filter we added was removed (for alert message) let defaultFilterStillExists = false; groups.forEach((group) => { group.filters.forEach(filter => { if (filter && filter.column && filter.filter) { const filterWasOnDefault = this.defaultFilterAdded.filters.some(defaultFilter => { return defaultFilter.column.prop === filter.column.prop && defaultFilter.filter.api === filter.filter.api; }); if (filterWasOnDefault) { defaultFilterStillExists = true; } } }); }); if (!defaultFilterStillExists) { this.defaultFilterAdded = undefined; } } this.updateBadgeNumber(); } updateBadgeNumber () { const filterTabIndex = this.tabs.findIndex((tab) => { return tab.context === ManageWidgetTabs.FILTERS; }); if (filterTabIndex > -1) { const badgeNumber = this.filterBuilderService.getBadgeNumber(this.currentGroups); this.tabs = [ ...this.tabs.slice(0, filterTabIndex), { ...this.tabs[filterTabIndex], badgeNumber }, ...this.tabs.slice(filterTabIndex + 1) ]; } } activeTabChanged (index: number) { this.initialGroups = this.currentGroups; this.activeTab = this.tabs[index].context; } typeChanged (type: Dashboards.WidgetType, isInit = false) { const { aggregateSupported, groupingSupported, sortingSupported, drilldownSupported, legendSupported, axisLabelSupported, secondaryGroupingSupported, dataTabEnabled } = this.dashboardsService.getSupportedBooleans(type, this.formGroup.value.object); if (!isInit) { if (!aggregateSupported && this.aggregateSupported) { this.formGroup.get('aggregateColumn').setValue(null); this.formGroup.get('aggregationType').setValue(null); } if (!groupingSupported && this.groupingSupported) { this.formGroup.get('groupColumn').setValue(null); } if (!secondaryGroupingSupported && this.secondaryGroupingSupported) { this.formGroup.get('subGroupColumn').setValue(null); } if (!sortingSupported && this.sortingSupported) { this.formGroup.get('sortColumn').setValue(null); } } this.dataTabSupported = dataTabEnabled; this.aggregateSupported = aggregateSupported && !this.isSummaryRecordType; this.groupingSupported = groupingSupported && !this.isSummaryRecordType; this.secondaryGroupingSupported = secondaryGroupingSupported && !this.isSummaryRecordType; this.sortingSupported = sortingSupported; this.drilldownSupported = drilldownSupported && !this.isSummaryRecordType; this.filteringSupported = !this.isSummaryRecordType || type === 'table'; this.legendSupported = legendSupported; this.axisLabelSupported = axisLabelSupported; this.setSortingOptions(); this.setMaxDataLabel(); this.setTabs(); this.setDataTabForHidden(); if (!isInit) { this.setValidity(); this.setSummaryColumns(type); } } setSummaryColumns (type: Dashboards.WidgetType) { if (type === 'stat' && this.isSummaryRecordType) { this.tableColumnsChanged([{ column: null, columnNameOverride: null }]); } } setTabs () { this.tabs = [{ label: this.i18n.translate( 'GLOBAL:textDetails', {}, 'Details' ), active: true, context: ManageWidgetTabs.DETAILS }, this.dataTabSupported ? { label: this.i18n.translate( 'GLOBAL:textData', {}, 'Data' ), active: false, context: ManageWidgetTabs.DATA } : null, this.filteringSupported ? { label: this.i18n.translate( 'GLOBAL:textFiltering', {}, 'Filtering' ), active: false, context: ManageWidgetTabs.FILTERS, badgeNumber: this.filterBuilderService.getBadgeNumber(this.initialGroups) } : null, this.drilldownSupported ? { label: this.i18n.translate( 'GLOBAL:textDrilldown', {}, 'Drill down' ), active: false, context: ManageWidgetTabs.DRILLDOWN } : null].filter((tab) => !!tab); } setMaxDataLabel () { const type = this.formGroup.value.type; switch (type) { case 'bar': case 'line': this.maxDataLabel = this.i18n.translate( 'common:textMaxAmountOfDataPoints', {}, 'and have a maximum amount of data points of' ); break; case 'pie': this.maxDataLabel = this.i18n.translate( 'common:textWithMaxAmountPieSlices', {}, 'with a maximum amount of pie slices of' ); break; case 'stat': case 'table': default: this.maxDataLabel = ''; break; } } objectChanged ( objectName: RootObjectNames, isInit = false ) { this.setTypeOptions(); this.updateColumnOptions(objectName); this.summaryRecordLabel = this.i18n.translate( 'common:textIWantToReportOnTheFollowingRecord', { record: (this.adHocDefinitions[objectName]?.display ?? '').toLowerCase() }, 'I want to report on the following __record__' ); if (!isInit) { // Object change should clear drilldown columns and add a default filter of last 365 days const defaultFilters = this.getDefaultWidgetFilters(objectName); this.initialGroups = this.filterBuilderService.remapColumns( this.availableFilterColumns, [defaultFilters], false ); this.currentGroups = this.initialGroups; this.defaultFilterAdded = defaultFilters; this.updateBadgeNumber(); this.simpleDrilldownColumns = []; this.drilldownColumns = []; this.resetAggregateColumn(); this.clearSorting(); } this.setSummaryOptions(objectName); this.typeChanged(this.formGroup.value.type); this.realignOptions(isInit); } setSummaryOptions (objectName: RootObjectNames) { const object = this.adHocDefinitions[objectName]; this.isSummaryRecordType = object.supportsSummaryWidgets; if (object.recordOptions) { this.summaryRecordOptions = object.recordOptions; this.hasRecordOptions = object.recordOptions.length > 0; } } updateColumnOptions (objectName: RootObjectNames) { this.currentBuckets = null; const formIds = this.setFormIds(); this.currentBuckets = this.adHocService.getBuckets( objectName, formIds, [], AdHocReportingUI.Usage.DASHBOARDS ); const columns = this.currentBuckets.reduce((acc, bucket) => { return [ ...acc, ...bucket.allColumns ]; }, [] as AdHocReportingUI.ColumnImplementation[]); this.setAllColumnOptionsHelperMap(columns); this.allColumnOptions = this.arrayHelper.sort( columns.map>(column => { const def = column.definition; let columnDisplay = ''; const isIdColumn = def.column === 'id'; const showAlias = isIdColumn || (def.dashboardAliasDisplay && def.dashboardAliasKey); if (showAlias) { if (isIdColumn) { columnDisplay = def.parentBucketName; } else { columnDisplay = this.i18n.translate( def.dashboardAliasKey, {}, def.dashboardAliasDisplay ); } } else { columnDisplay = def.display; } return { label: !showAlias ? `${columnDisplay} (${def.parentBucketName})` : columnDisplay, htmlLabel: !showAlias ? this.dashboardsService.getHtmlColumnLabel( columnDisplay, def.parentBucketName ) : null, value: `${def.parentBucket}.${def.column}`, option: !showAlias ? this.dashboardsService.getHtmlOptionLabel( columnDisplay, def.parentBucketName ) : null }; }), 'label' ); this.availableFilterColumns = this.dashboardsService.adHocColumnToFilterColumn( columns.filter((col) => { // Filter form id out because it is only used for aggregate col const def = col.definition; if (def.parentBucket === 'form' && def.column === 'id') { return false; } return !col.definition.noFiltering; }).map(column => column.definition) ); } setAllColumnOptionsHelperMap (columns: AdHocReportingUI.ColumnImplementation[] = []) { const map: SimpleStringMap = {}; columns.forEach((column) => { const val = `${column.definition.parentBucket}.${column.definition.column}`; map[val] = column.definition; }); this.allColumnOptionsHelperMap = map; } resetAggregateColumn () { const aggregateCol = this.formGroup.get('aggregateColumn'); if (aggregateCol.value) { const aggregateType = this.formGroup.value.aggregationType; if (aggregateType === AdHocReportingAPI.ChartAggregateType.Count) { aggregateCol.setValue(`${this.getObjectPropertyForAggregation()}.id`); } else { aggregateCol.setValue(null); } } } getObjectPropertyForAggregation () { let object: any = this.formGroup.value.object; if (object) { if (object.toLowerCase().includes('inkind')) { object = 'inKindItem'; } else if (object.toLowerCase().includes('customform')) { object = 'form'; } } return object; } aggregateTypeChanged () { this.formGroup.get('aggregateColumn').setValue(null); this.realignOptions(); } aggregateColumnChanged () { this.clearSorting(); this.setSortingOptions(); this.realignOptions(); } groupingColumnChanged () { this.clearSorting(); this.setSortingOptions(); this.realignOptions(); } setSortingOptions () { if (this.sortingSupported) { if (!this.formGroup.value.groupColumn || !this.formGroup.value.aggregateColumn) { this.clearSorting(); } else { const foundAggregate = this.allColumnOptions.find((col) => { return col.value === this.formGroup.value.aggregateColumn; }); const foundGroup = this.allColumnOptions.find((col) => { return col.value === this.formGroup.value.groupColumn; }); this.sortColumnOptions = [{ label: foundGroup.label, htmlLabel: foundGroup.htmlLabel, option: foundGroup.option, value: this.formGroup.value.groupColumn }, { label: foundAggregate.label, htmlLabel: foundAggregate.htmlLabel, option: foundAggregate.option, value: this.formGroup.value.aggregateColumn }]; if (!this.formGroup.value.sortColumn) { this.formGroup.get('sortColumn').setValue( this.formGroup.value.aggregateColumn ); } } } else { this.clearSorting(); } } setDataTabForHidden () { if (!this.dataTabSupported) { const aggregate = this.allColumnOptions.find(option => { const column = this.allColumnOptionsHelperMap[option.value]; return column.supportsMinMaxSum && option.value.startsWith(this.formGroup.value.object); }); const groupColumn = this.allColumnOptions.find(option => { const column = this.allColumnOptionsHelperMap[option.value]; return column.supportsGrouping && option.value.startsWith(this.formGroup.value.object); }); this.formGroup.get('aggregateColumn').setValue(aggregate.value); this.formGroup.get('groupColumn').setValue(groupColumn.value); this.formGroup.get('aggregationType').setValue(AdHocReportingAPI.ChartAggregateType.Sum); } } clearSorting () { this.sortColumnOptions = []; this.formGroup.get('sortColumn').setValue(null); } realignOptions (isInit = false) { this.aggregateColumnOptions = this.allColumnOptions.filter(columnOption => { return this.passesAggregationCheck(columnOption.value) && ![ this.formGroup.value.groupColumn, this.formGroup.value.subGroupColumn ].includes(columnOption.value); }); this.groupColumnOptions = this.allColumnOptions.filter(columnOption => { const isGroupable = this.allColumnOptionsHelperMap[ columnOption.value ].supportsGrouping; return isGroupable && ![ this.formGroup.value.aggregateColumn, this.formGroup.value.subGroupColumn ].includes(columnOption.value); }); this.subGroupColumnOptions = this.allColumnOptions.filter(columnOption => { const def = this.allColumnOptionsHelperMap[ columnOption.value ]; const isGroupable = def.supportsGrouping && !def.noSubGrouping; return isGroupable && ![ this.formGroup.value.aggregateColumn, this.formGroup.value.groupColumn ].includes(columnOption.value); }); if (!isInit) { this.setSortingOptions(); this.setValidity(); } } passesAggregationCheck (value: string) { const type = this.formGroup.value.aggregationType; const definition = this.allColumnOptionsHelperMap[value]; switch (type) { case AdHocReportingAPI.ChartAggregateType.Count: // count only supports the source object per Tommy & Dan 01/30/20 const object = this.getObjectPropertyForAggregation(); if (definition.parentBucket === object) { return definition.column === 'id'; } return false; default: return definition.supportsMinMaxSum; } } setValidity () { let isValid = this.formGroup.valid && this.filterBuilderValid; if (this.drilldownSupported) { isValid = isValid && this.drilldownValid; } // summary reports require at least one column if (this.isSummaryRecordType) { isValid = isValid && this.drilldownValid && (this.drilldownColumns.length > 0) && this.drilldownColumns.every(col => !!col?.definition); } if (this.formGroup.value.type === 'table') { isValid = isValid && this.tableColumnsValid && this.simpleDrilldownColumns.length > 0; } const invalidColumns = this.adHocService.getInvalidColumnHeaders( this.drilldownColumns.map((column) => { return column.columnNameOverride; }) ); const columnNamesValid = invalidColumns.length === 0; this.isValid = isValid && columnNamesValid; } async save () { const existingWidget = this.isUpdate ? this.existingWidget : null; const config: GCDashboards.WidgetConfig = { ...this.formGroup.value, id: existingWidget ? existingWidget.id : null, filters: [], drilldownColumns: this.drilldownColumns, x: existingWidget ? existingWidget.x || 0 : 0, y: existingWidget ? existingWidget.y || 0 : 0, width: existingWidget ? existingWidget.width || 6 : 6, height: existingWidget ? existingWidget.height || 6 : 6, advancedFilters: this.currentGroups, useAnd: this.filtersUsingAnd }; if (!existingWidget && config.type === 'stat') { config.width = 2; config.height = 2; } this.spinnerService.startSpinner(); await this.dashboardsService.saveWidget(config, this.dashboardId, this.isUpdate); this.spinnerService.stopSpinner(); this.closeModal.emit(config); this.analyticsService.emitEvent({ eventName: 'Manage widget modal save', eventType: EventType.Click, extras: null }); } ngOnDestroy () { this.sub.unsubscribe(); } // ** Start Validators ** // aggregateRequiredValidator () { return (abstractGroup: AbstractControl) => { const group = abstractGroup as TypeSafeFormGroup; const type = group.value.type; if (type) { const { aggregateSupported } = this.dashboardsService.getSupportedBooleans( type, group.value.object ); // aggregate isn't required for summary charts // since the chart is only about one record if (aggregateSupported && !this.isSummaryRecordType) { const errors: any = {}; if (!group.value.aggregateColumn) { errors.aggregateColumn = this.requiredError; } else if (!group.value.aggregationType) { errors.aggregationType = this.requiredError; } if (Object.keys(errors).length > 0) { return errors; } } } return null; }; } groupingRequiredValidator () { return (abstractGroup: AbstractControl) => { const group = abstractGroup as TypeSafeFormGroup; const type = group.value.type; if (type) { const { groupingSupported } = this.dashboardsService.getSupportedBooleans( type, group.value.object ); // grouping isn't required for summary charts // since the chart is only about one record if (groupingSupported && !this.isSummaryRecordType && !group.value.groupColumn) { return { groupColumn: this.requiredError }; } } return null; }; } maxGroupsValidator () { return (abstractGroup: AbstractControl) => { const group = abstractGroup as TypeSafeFormGroup; const type = group.value.type; if (type) { const groupColumn = group.value.groupColumn; const errors: any = {}; if (groupColumn) { const maxGroups = group.value.maxGroups; const groupError = this.doMaxGroupValidation( maxGroups, groupColumn ); if (groupError) { errors['maxGroups'] = groupError; } } if (Object.keys(errors).length > 0) { return errors; } } return null; }; } doMaxGroupValidation ( maxGroups: number, columnValue: string ) { if (!maxGroups) { return this.requiredError; } else if (maxGroups < 1) { return this.greaterThanZeroError; } else if (maxGroups > DEFAULT_MAX_GROUPS) { const definition = this.allColumnOptionsHelperMap[columnValue]; if (definition) { const isDate = definition.column.endsWith('Year') || definition.column.endsWith('Month'); if (!isDate) { return { betweenOneAndTen: { i18nKey: 'common:textCannotBeGreaterThanTwenty', defaultValue: 'Cannot be greater than twenty' } }; } } } return null; } sortColumnValidator () { return (abstractGroup: AbstractControl) => { const group = abstractGroup as TypeSafeFormGroup; const type = group.value.type; if (type) { const { sortingSupported } = this.dashboardsService.getSupportedBooleans( type, group.value.object ); if (sortingSupported) { const errors: any = {}; const sortAscending = group.value.sortAscending; if (!group.value.sortColumn) { errors.sortColumn = this.requiredError; } else if (!group.value.sortAscending && sortAscending !== false) { errors.sortAscending = this.requiredError; } if (Object.keys(errors).length > 0) { return errors; } } } return null; }; } customFormValidator () { return (group: AbstractControl) => { const object = group.value.object; if (object === 'customForm') { if (!group.value.primaryFormId) { return { primaryFormId: this.requiredError }; } } return null; }; } tableRowOptionsValidator () { return (group: AbstractControl) => { const type = group.value.type; if (type === 'table') { const rowsPerPage = group.value.rowsPerPage; const hasMaxRows = group.value.hasMaxRows; const maxRows = group.value.maxRows; const errors: any = {}; if (rowsPerPage <= 0) { errors.rowsPerPage = this.greaterThanZeroError; } if (hasMaxRows && maxRows <= 0) { errors.maxRows = this.greaterThanZeroError; } if (Object.keys(errors).length > 0) { return errors; } } return null; }; } summaryRecordRequiredValidator () { return (group: TypeSafeFormGroup) => { const type = group.value.type; if (!this.isSummaryRecordType) { return null; } if (type === 'pie' || type === 'stat') { if (!group.value.summaryRecordId) { return { summaryRecordId: { required: true } }; } } return null; }; } // ** End Validators ** // }