File

src/app/modules/dashboard/services/report/report.service.ts

Index

Properties
Methods

Constructor

constructor(sanitizer: DomSanitizer, usageService: UsageService, userService: UserService, configService: ConfigService, baseReportService: BaseReportService, permissionService: PermissionService, courseProgressService: CourseProgressService, searchService: SearchService, frameworkService: FrameworkService, profileService: ProfileService)
Parameters :
Name Type Optional
sanitizer DomSanitizer No
usageService UsageService No
userService UserService No
configService ConfigService No
baseReportService BaseReportService No
permissionService PermissionService No
courseProgressService CourseProgressService No
searchService SearchService No
frameworkService FrameworkService No
profileService ProfileService No

Methods

addReportSummary
addReportSummary(undefined)
Parameters :
Name Optional
No
Returns : Observable<any>
Public addSummary
addSummary(body: object)
Parameters :
Name Type Optional
body object No
Returns : Observable<any>
Public downloadMultipleDataSources
downloadMultipleDataSources(dataSources: IDataSource[])
Parameters :
Name Type Optional
dataSources IDataSource[] No
Returns : any
Public downloadReport
downloadReport(filePathSource: string)
Parameters :
Name Type Optional
filePathSource string No
Returns : any
Public fetchDataSource
fetchDataSource(filePath: string, id?: string | number)
Parameters :
Name Type Optional
filePath string No
id string | number Yes
Returns : Observable<any>
Public fetchReportById
fetchReportById(id, hash?: string)
Parameters :
Name Type Optional
id No
hash string Yes
Private getDataSourceById
getDataSourceById(dataSources: literal type[], id: string)
Parameters :
Name Type Optional Default value
dataSources literal type[] No
id string No 'default'
Returns : any
Public getFileMetaData
getFileMetaData(dataSources: IDataSource[])

to enable backward compatibilty taking filenames from the last

```html
Parameters :
Name Type Optional
dataSources IDataSource[] No
Example :
because some reports use /reports/:slug/:filename
while some other reports use /reports/fetch/:slug/:filename
Returns : Observable<any>
Public getFlattenedReports
getFlattenedReports(reports: any[])
Parameters :
Name Type Optional
reports any[] No
Returns : any
Public getFormattedDate
getFormattedDate(dateString)
Parameters :
Name Optional
dateString No
Returns : any
Public getLatestLastModifiedOnDate
getLatestLastModifiedOnDate(data: literal type[], dataSourceIds?: literal type)
Parameters :
Name Type Optional
data literal type[] No
dataSourceIds literal type Yes
Returns : any
Public getLatestSummary
getLatestSummary(undefined)
Parameters :
Name Optional
No
Returns : Observable<any>
Public getMaterializedChildRows
getMaterializedChildRows(reports: any[])
Parameters :
Name Type Optional
reports any[] No
Returns : any
Public getParameterizedFiles
getParameterizedFiles(files: literal type[], hash?: string)
Parameters :
Name Type Optional
files literal type[] No
hash string Yes
Returns : any
Public getParameterValues
getParameterValues(parameter: string)
Parameters :
Name Type Optional
parameter string No
Returns : literal type
Private getTableData
getTableData(data: literal type[], tableId)
Parameters :
Name Type Optional
data literal type[] No
tableId No
Returns : any
Public getUpdatedParameterizedPath
getUpdatedParameterizedPath(dataSources: IDataSource[], hash?: string)
Parameters :
Name Type Optional
dataSources IDataSource[] No
hash string Yes
Returns : any
Public isAuthenticated
isAuthenticated(roles: string | undefined)
Parameters :
Name Type Optional
roles string | undefined No
Returns : Observable<boolean>
Public isUserReportAdmin
isUserReportAdmin()
Returns : boolean
Public isUserSuperAdmin
isUserSuperAdmin()
Returns : boolean
Public listAllReports
listAllReports(filters: IListReportsFilter)
Parameters :
Name Type Optional Default value
filters IListReportsFilter No {}
Public overlayMultipleDataSources
overlayMultipleDataSources(dataSources: (T[])[], commonDimension: U)
Type parameters :
  • T
  • U
Parameters :
Name Type Optional
dataSources (T[])[] No
commonDimension U No
Returns : any
Public prepareChartData
prepareChartData(chartsArray: Array, data: literal type[], downloadUrl: IDataSource[], reportLevelDataSourceId: string)
Parameters :
Name Type Optional
chartsArray Array<any> No
data literal type[] No
downloadUrl IDataSource[] No
reportLevelDataSourceId string No
Public prepareTableData
prepareTableData(tablesArray: any, data: any, downloadUrl: string, hash?: string)
Parameters :
Name Type Optional
tablesArray any No
data any No
downloadUrl string No
hash string Yes
Public publishReport
publishReport(reportId: string, hash?: string)
Parameters :
Name Type Optional
reportId string No
hash string Yes
Returns : any
Public resolveParameterizedPath
resolveParameterizedPath(path: string, explicitValue?: string)
Parameters :
Name Type Optional
path string No
explicitValue string Yes
Returns : string
Public retireReport
retireReport(reportId: string, hash?: string)
Parameters :
Name Type Optional
reportId string No
hash string Yes
Returns : any
Public transformHTML
transformHTML(data: any)
Parameters :
Name Type Optional
data any No
Returns : any
Public updateReport
updateReport(reportId: string, body: object)
Parameters :
Name Type Optional
reportId string No
body object No
Returns : Observable<any>

Properties

Private _superAdminSlug
Type : string
Private cachedMapping
Type : object
Default value : {}
Public convertToBase64
Default value : () => {...}
getChartData
Default value : () => {...}
Public getParameterFromHash
Default value : () => {...}
Public getParametersHash
Default value : () => {...}
Public isReportParameterized
Default value : () => {...}
import { ProfileService } from '@sunbird/profile';
import { CourseProgressService } from './../course-progress/course-progress.service';
import { IListReportsFilter, IReportsApiResponse, IDataSource } from './../../interfaces';
import { ConfigService, IUserData } from '@sunbird/shared';
import { UserService, BaseReportService, PermissionService, SearchService, FrameworkService } from '@sunbird/core';
import { Injectable } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { UsageService } from '../usage/usage.service';
import { map, catchError, pluck, mergeMap, shareReplay } from 'rxjs/operators';
import * as _ from 'lodash-es';
import { Observable, of, forkJoin } from 'rxjs';
import  dayjs from 'dayjs';
import { v4 as UUID } from 'uuid';

const PRE_DEFINED_PARAMETERS = ['$slug', '$board', '$state', '$channel'];


@Injectable({
  providedIn:'root'
})
export class ReportService  {

  private _superAdminSlug: string;

  private cachedMapping = {};

  constructor(private sanitizer: DomSanitizer, private usageService: UsageService, private userService: UserService,
    private configService: ConfigService, private baseReportService: BaseReportService, private permissionService: PermissionService,
    private courseProgressService: CourseProgressService, private searchService: SearchService,
    private frameworkService: FrameworkService, private profileService: ProfileService ) {
    try {
      this._superAdminSlug = (<HTMLInputElement>document.getElementById('superAdminSlug')).value;
    } catch (error) {
      this._superAdminSlug = 'sunbird';
    }
  }

  public fetchDataSource(filePath: string, id?: string | number): Observable<any> {
    return this.usageService.getData(filePath).pipe(
      map(configData => {
        return {
          loaded: true,
          result: _.get(configData, 'result'),
          ...(id && { id })
        };
      })
      , catchError(error => of({ loaded: false }))

    );
  }

  public downloadMultipleDataSources(dataSources: IDataSource[]) {
    if (!dataSources.length) {
      // for India heat map scenario.
      return of([]);
    }
    const apiCalls = _.map(dataSources, (source: IDataSource) => {
      return this.fetchDataSource(_.get(source, 'path'), _.get(source, 'id'));
    });
    return forkJoin(...apiCalls).pipe(
      mergeMap(response => {

        response = response.filter(function(item) { if (item ) { return item.loaded = true; } });
        return this.getFileMetaData(dataSources).pipe(
          map(metadata => {
            return _.map(response, res => {
              res.lastModifiedOn = _.get(metadata[res.id], 'lastModified');
              return res;
            });
          })
        );
      })
    );
  }

  public fetchReportById(id, hash?: string): Observable<IReportsApiResponse> {
    const req = {
      url: `${this.configService.urlConFig.URLS.REPORT.READ}/${id}`
    };
    if (hash) {
      req.url = `${this.configService.urlConFig.URLS.REPORT.READ}/${id}/${hash}`;
    }
    return this.baseReportService.get(req).pipe(
      map(apiResponse => _.get(apiResponse, 'result'))
    );
  }

  /**
   * @description publishes a report as live
   * @param {string} reportId
   * @returns
   * @memberof ReportService
   */
  public publishReport(reportId: string, hash?: string) {
    const req = {
      url: `${this.configService.urlConFig.URLS.REPORT.PUBLISH}/${reportId}`
    };
    if (hash) {
      req.url = `${this.configService.urlConFig.URLS.REPORT.PUBLISH}/${reportId}/${hash}`;
    }
    return this.baseReportService.get(req).pipe(
      map(apiResponse => _.get(apiResponse, 'result'))
    );
  }

  /**
   * @description retires a report and deactivates all jobs associated with this report. 
   * @param {string} reportId
   * @returns
   * @memberof ReportService
   */
  public retireReport(reportId: string, hash?: string) {
    const req = {
      url: `${this.configService.urlConFig.URLS.REPORT.RETIRE}/${reportId}`
    };
    if (hash) {
      req.url = `${this.configService.urlConFig.URLS.REPORT.RETIRE}/${reportId}/${hash}`;
    }
    return this.baseReportService.get(req).pipe(
      map(apiResponse => _.get(apiResponse, 'result'))
    );
  }

  public listAllReports(filters: IListReportsFilter = {}): Observable<IReportsApiResponse> {
    const request = {
      url: this.configService.urlConFig.URLS.REPORT.LIST,
      data: {
        'request': {
          'filters': filters
        }
      }
    };
    return this.baseReportService.post(request).pipe(
      map(apiResponse => _.get(apiResponse, 'result'))
    );
  }

  /**
   * @description api call to update an existing report
   * @param reportId
   * @param body
   */
  public updateReport(reportId: string, body: object): Observable<any> {
    const request = {
      url: `${this.configService.urlConFig.URLS.REPORT.UPDATE}/${reportId}`,
      data: {
        'request': {
          'report': body
        }
      }
    };
    return this.baseReportService.patch(request).pipe(
      map(apiResponse => _.get(apiResponse, 'result'))
    );
  }

  /**
   * @description adds a report and chart level summary to an existing report
   * @param body
   */
  public addSummary(body: object): Observable<any> {
    const request = {
      url: `${this.configService.urlConFig.URLS.REPORT.SUMMARY.CREATE}`,
      data: {
        'request': {
          'summary': body
        }
      }
    };
    return this.baseReportService.post(request).pipe(
      map(apiResponse => _.get(apiResponse, 'result'))
    );
  }

  public prepareChartData(chartsArray: Array<any>, data: { result: any, id: string, lastModifiedOn: undefined | string }[],
    downloadUrl: IDataSource[], reportLevelDataSourceId: string): Array<{}> {
    return _.map(chartsArray, chart => {
      const { chartType, dataSource = null, mapData = {} } = chart;
      const chartObj: any = {};
      chartObj.chartConfig = chart;
      if (!chartObj.chartConfig['id']) {
        chartObj.chartConfig['id']  = UUID();
      }
      chartObj.downloadUrl = downloadUrl;
      chartObj.chartData = dataSource ? this.getChartData(data, chart) :
        _.get(this.getDataSourceById(data, reportLevelDataSourceId || 'default'), 'data');
        if(chartObj.chartConfig.id === 'Big_Number' && chartObj.chartData  === undefined){
          chartObj.chartData = [0];
        }
      chartObj.lastUpdatedOn = _.get(data, 'metadata.lastUpdatedOn') ||
        this.getLatestLastModifiedOnDate(data, dataSource || { ids: [reportLevelDataSourceId || 'default'] });
      if (chartType && _.toLower(chartType) === 'map') {
        chartObj.mapData = {
          ...mapData,
          reportData: chartObj.chartData
        };
      }
      if (chartObj && chartObj.chartData && chartObj.chartData.length > 0) {
        return chartObj;
      }
    }).filter(function(chartData) {
       if (chartData ) {
          return (chartData['chartData'] != null || chartData['chartData'] != undefined);
        }
      });
  }

  public prepareTableData(tablesArray: any, data: any, downloadUrl: string, hash?: string): Array<{}> {
    tablesArray = _.isArray(tablesArray) ? tablesArray : [tablesArray];
    return _.map(tablesArray, table => {
      const tableId = _.get(table, 'id') || `table-${_.random(1000)}`;
      const dataset = this.getTableData(data, _.get(table, 'id'));
      const tableData: any = {};
      tableData.id = tableId;
      tableData.name = _.get(table, 'name') || 'Table';
      tableData.config = _.get(table, 'config') ||  false;
      if (!tableData.config) {
        tableData.data = _.get(table, 'values') || _.get(dataset, _.get(table, 'valuesExpr'));
      } else {
        tableData.data = dataset.data;
      }
      tableData.header = _.get(table, 'columns') || _.get(dataset, _.get(table, 'columnsExpr'));
      tableData.downloadUrl = this.resolveParameterizedPath(_.get(table, 'downloadUrl') || downloadUrl,
        hash ? this.getParameterFromHash(hash) : null);
      return tableData;

    });
  }

  public getParameterizedFiles(files: { downloadUrl: string }[], hash?: string) {
    return files.map(file => ({
      ...file,
      downloadUrl: this.resolveParameterizedPath(file.downloadUrl, hash ? this.getParameterFromHash(hash) : null)
    }));
  }

  private getTableData(data: { result: any, id: string }[], tableId) {
    if (data.length === 1) {
      const [dataSource] = data;
      if (dataSource.id === 'default') {
        return dataSource.result;
      }
    }
    return this.getDataSourceById(data, tableId) || {};
  }

  getChartData = (data: { result: any, id: string }[], chart: any) => {
    const chartDataSource = _.get(chart, 'dataSource');

    if (chartDataSource.ids.length === 1) {
      return _.get(this.getDataSourceById(data, _.head(chartDataSource.ids)), 'data');
    }
    // overlay if multiple dataSources present
    const datasources = _.map(chartDataSource.ids, id => {
      return _.get(this.getDataSourceById(data, id), 'data');
    });

    const result = this.overlayMultipleDataSources(datasources as Array<any[]>, _.get(chartDataSource, 'commonDimension'));
    return result;
  }

  public getLatestLastModifiedOnDate(data: { result: any, id: string, lastModifiedOn: undefined | string }[],
    dataSourceIds?: { ids: string[] }) {
    let queryObj = data;
    if (dataSourceIds) {
      queryObj = _.filter(data, obj => _.includes(dataSourceIds.ids, obj.id));
    }
    return Date.parse(_.get(_.maxBy(queryObj, val => Date.parse(val.lastModifiedOn)), 'lastModifiedOn'));
  }

  private getDataSourceById(dataSources: { result: any, id: string }[], id: string = 'default') {
    return _.get(_.find(dataSources, ['id', id]), 'result');
  }

  /**
   * @description Overlays multiple datasource with common dimension.
   * @template T
   * @template U
   * @param {(T[])[]} dataSources
   * @param {U} commonDimension
   * @returns
   * @memberof ReportService
   */
  public overlayMultipleDataSources<T, U extends keyof T>(dataSources: (T[])[], commonDimension: U) {
    return _.flatten(dataSources);
  }

  /**
   * @description downloads a csv file from blob storage
   * @param {string} filePathSource
   * @returns
   * @memberof ReportService
   */
  public downloadReport(filePathSource: string) {
    return this.fetchDataSource(filePathSource).pipe(
      map(response => _.get(response, 'result.signedUrl'))
    );
  }

  /**
   * @description returns true or false based on authentication of roles
   * @param {(string | undefined)} roles
   * @returns {Observable<boolean>}
   * @memberof ReportService
   */
  public isAuthenticated(roles: string | undefined): Observable<boolean> {

    return this.userService.userData$.pipe(
      map((user: IUserData) => {
        if (user && !user.err) {
          const allowedRoles = this.configService.rolesConfig.ROLES[roles];
          const userRoles = user.userProfile.userRoles;
          return _.intersection(allowedRoles, userRoles).length;
        } else {
          return false;
        }
      }),
      catchError(err => of(false))
    );
  }

  /**
 * @description checks whether user is an REPORT ADMIN or not
 */
  public isUserReportAdmin(): boolean {
    if (this.userService.userProfile) {
      return _.includes(_.get(this.userService, 'userProfile.userRoles'), 'REPORT_ADMIN');
    }
    return false;
  }

  public isUserSuperAdmin(): boolean {
    if (!this.isUserReportAdmin()) { return false; }
    return _.get(this.userService, 'userProfile.rootOrg.slug') === this._superAdminSlug;
  }

  public transformHTML(data: any) {
    return this.sanitizer.bypassSecurityTrustHtml(data);
  }

  /**
   * @description calls the api to add report and chart level summary
   */
  addReportSummary({ reportId, summaryDetails }): Observable<any> {
    const type = _.get(summaryDetails, 'type');
    const reqBody = {
      reportid: reportId,
      createdby: this.userService.userid,
      summary: summaryDetails.summary,
      ...(summaryDetails.hash && { param_hash: summaryDetails.hash }),
      ...(type === 'chart' && { chartid: summaryDetails.chartId })
    };
    return this.addSummary(reqBody);
  }

  /**
 * @description - fetches the report metadata from the blob
 * to enable backward compatibilty taking filenames from the last
 * @example because some reports use /reports/:slug/:filename
 * @example while some other reports use /reports/fetch/:slug/:filename
 * @param {IDataSource[]} dataSources
 * @returns
 * @memberof ReportService
 */
  public getFileMetaData(dataSources: IDataSource[]): Observable<any> {

    const mappedfileIdWithPath = _.mapValues(_.keyBy(dataSources, 'id'), 'path');
    _.forIn(mappedfileIdWithPath, (val, key, object) => {
      object[key] = _.join(_.slice(_.split(val, '/'), -2), '/');
    });

    const requestParams = {
      params: {
        fileNames: JSON.stringify(mappedfileIdWithPath)
      }
    };

    return this.courseProgressService.getReportsMetaData(requestParams)
      .pipe(
        pluck('result'),
        catchError(err => of({}))
      );
  }

  public getFormattedDate(dateString) {
    return dayjs(dateString).format('DD-MMMM-YYYY');
  }

  /**
   * @description calls the API to fetch latest report and chart level summary
   */
  public getLatestSummary({ reportId, chartId = null, hash = null }): Observable<any> {
    const url = `${this.configService.urlConFig.URLS.REPORT.SUMMARY.PREFIX}/${reportId}`;
    const req = {
      url: chartId ? `${url}/${chartId}` : url
    };
    if (hash) { req.url = `${req.url}?hash=${hash}`; }
    return this.baseReportService.get(req).pipe(
      map(apiResponse => _.get(apiResponse, 'result.summaries')),
      catchError(err => of([]))
    );
  }


  public getParameterValues(parameter: string): { value: string, masterData: () => Observable<string[]> } {
    const parameterMappings = {
      $slug: {
        value: _.get(this.userService, 'userProfile.rootOrg.slug'),
        masterData: () => {
          if (!this.cachedMapping.hasOwnProperty('$slug')) {
            const req = {
              filters: { isRootOrg: true, status: 1 },
              fields: ['id', 'channel', 'slug', 'orgName'],
              pageNumber: 1,
              limit: 10000
            };
            this.cachedMapping['$slug'] = this.searchService.orgSearch(req).pipe(
              map(res => _.map(_.get(res, 'result.response.content'), 'slug')),
              shareReplay(1)
            );
          }
          return this.cachedMapping['$slug'];
        }
      },
      $board: {
        value: _.get(this.userService, 'userProfile.framework.board[0]'),
        masterData: () => {
          if (!this.cachedMapping.hasOwnProperty('$board')) {
            this.cachedMapping['$board'] = this.frameworkService.getChannel(_.get(this.userService, 'hashTagId'))
              .pipe(
                mergeMap(channel => this.frameworkService.getFrameworkCategories(_.get(channel, 'result.channel.defaultFramework'))
                  .pipe(
                    map(framework => {
                      const frameworkData = _.get(framework, 'result.framework');
                      const boardCategory = _.find(frameworkData.categories, ['code', 'board']);
                      if (!boardCategory) { return of([]); }
                      return _.map(boardCategory.terms, 'name');
                    }),
                    shareReplay(1),
                  )),
                catchError(err => of([]))
              );
          }
          return this.cachedMapping['$board'];
        }
      },
      $state: {
        value: _.get(_.find(_.get(this.userService, 'userProfile.userLocations'), ['type', 'state']), 'name'),
        masterData: () => {
          if (!this.cachedMapping.hasOwnProperty('$state')) {
            const requestData = { 'filters': { 'type': 'state' } };
            this.cachedMapping['$state'] = this.profileService.getUserLocation(requestData).pipe(
              map(apiResponse => _.map(_.get(apiResponse, 'result.response'), state => _.get(state, 'name'))),
              shareReplay(1),
              catchError(err => of([]))
            );
          }
          return this.cachedMapping['$state'];
        }
      },
      $channel: {
        value: _.get(this.userService, 'userProfile.rootOrg.hashTagId'),
        masterData: () => {
          if (!this.cachedMapping.hasOwnProperty('$channel')) {
            const req = {
              filters: { isRootOrg: true, status: 1 },
              fields: ['id', 'channel', 'slug', 'orgName'],
              pageNumber: 1,
              limit: 10000
            };
            this.cachedMapping['$channel'] = this.searchService.orgSearch(req).pipe(
              map(res => _.map(_.get(res, 'result.response.content'), 'id')),
              shareReplay(1)
            );
          }
          return this.cachedMapping['$channel'];
        }
      }
    };

    return parameterMappings[parameter];
  }

  public convertToBase64 = (value) => btoa(value);

  public getParametersHash = (report: { parameters: string[] }) => {
    const parameters = _.get(report, 'parameters');
    return this.convertToBase64(_.join(_.map(parameters, param => {
      return _.replace(param, _.toLower(param), this.getParameterValues(param).value);
    }), '__'));
  }

  public getParameterFromHash = (hash: string) => {
    return _.split(atob(hash), '__');
  }

  public isReportParameterized = report => _.get(report, 'parameters.length') > 0 && _.isArray(report.parameters) && _.get(report, 'isParameterized');

  public resolveParameterizedPath(path: string, explicitValue?: string): string {
    return _.reduce(PRE_DEFINED_PARAMETERS, (result: string, parameter: string) => {
      if (_.includes(result, parameter) && this.getParameterValues(parameter)) {
        result = _.replace(result, parameter, explicitValue || this.getParameterValues(parameter).value);
      }
      return result;
    }, path);
  }

  public getUpdatedParameterizedPath(dataSources: IDataSource[], hash?: string) {
    const explicitValue = hash ? this.getParameterFromHash(hash) : null;
    return _.map(dataSources, (dataSource: IDataSource) => ({
      id: dataSource.id,
      path: this.resolveParameterizedPath(dataSource.path, explicitValue)
    }));
  }

  public getMaterializedChildRows(reports: any[]) {
    const apiCall = _.map(reports, report => {
      const isParameterized = _.get(report, 'isParameterized') || false;
      if (!isParameterized) { return of(report); }
      const parameters = _.get(report, 'parameters');
      if (!parameters.length) { return of(report); }
      const paramObj: { masterData: () => Observable<any> } = this.getParameterValues(_.toLower(parameters[0]));
      if (!paramObj) { return of(report); }
      return paramObj.masterData()
        .pipe(
          map(results => {
            report.children = _.uniqBy(_.concat(_.map(report.children, child => ({
              ...child,
              label: this.getParameterFromHash(child.hashed_val)
            })),
              _.map(results, res => ({
                label: res,
                hashed_val: this.convertToBase64(_.split(res, '__')),
                status: 'draft',
                reportid: _.get(report, 'reportid'),
                materialize: true
              }))), 'hashed_val');
            return report;
          }),
          catchError(err => {
            // console.error(err);
            return of(report);
          }));
    });
    return forkJoin(apiCall);
  }

  public getFlattenedReports(reports: any[]) {
    return _.reduce(reports, (result, report) => {
      const isParameterized = _.get(report, 'isParameterized') || false;
      if (isParameterized && _.get(report, 'children.length')) {
        for (const childReport of report.children) {
          const flattenedReport = _.assign({ ...report }, _.omit(childReport, 'id'));
          delete flattenedReport.children;
          result.push(flattenedReport);
        }
        return result;
      }
      result.push(report);
      return result;
    }, []);
  }

}

results matching ""

    No results matching ""