import { Component , OnDestroy , OnInit , TemplateRef , ViewChild } from '@angular/core' ; import { BehaviorSubject , ReplaySubject } from 'rxjs' ; import { CmdbType } from '../../models/cmdb-type' ; import { ActivatedRoute , Data , Router } from '@angular/router' ; import { takeUntil } from 'rxjs/operators' ; import { RenderResult } from '../../models/cmdb-render' ; import { TableComponent } from '../../../layout/table/table.component' ; import { Column , Sort , SortDirection , TableState , TableStatePayload } from '../../../layout/table/table.types' ; import { ObjectService } from '../../services/object.service' ; import { CollectionParameters } from '../../../services/models/api-parameter' ; import { APIGetMultiResponse } from '../../../services/models/api-response' ; import { CmdbMode } from '../../modes.enum' ; import { FileService } from '../../../export/export.service' ; import { FileSaverService } from 'ngx-filesaver' ; import { ToastService } from '../../../layout/toast/toast.service' ; import { SidebarService } from '../../../layout/services/sidebar.service' ; import { NgbModal , NgbModalRef } from '@ng-bootstrap/ng-bootstrap' ; import { ObjectsDeleteModalComponent } from '../modals/objects-delete-modal/objects-delete-modal.component' ; import { UserSetting } from '../../../management/user-settings/models/user-setting' ; import { UserSettingsDBService } from '../../../management/user-settings/services/user-settings-db.service' ; import { convertResourceURL , UserSettingsService } from '../../../management/user-settings/services/user-settings.service' ; import { SupportedExporterExtension } from '../../../export/export-objects/model/supported-exporter-extension' ; @ Component ( { selector : 'cmdb-objects-by-type' , templateUrl : './objects-by-type.component.html' , styleUrls : [ './objects-by-type.component.scss' ] } ) export class ObjectsByTypeComponent implements OnInit , OnDestroy { /**
     * HTML ID of the table.
     * Used for user settings and table-states
     */ public readonly id : string = 'table-objects-type' ; /**
     * Table component.
     */ @ ViewChild ( TableComponent ) objectsTableComponent : TableComponent < RenderResult > ; @ ViewChild ( 'activeTemplate' , { static : true } ) activeTemplate : TemplateRef < any > ; @ ViewChild ( 'fieldTemplate' , { static : true } ) fieldTemplate : TemplateRef < any > ; @ ViewChild ( 'dateTemplate' , { static : true } ) dateTemplate : TemplateRef < any > ; @ ViewChild ( 'actionTemplate' , { static : true } ) actionTemplate : TemplateRef < any > ; /**
     * Component un-subscriber.
     */ private subscriber : ReplaySubject < void > = new ReplaySubject < void > ( ) ; /**
     * Current render results
     */ public results : Array < RenderResult > = [ ] ; /**
     * Selected objects
     */ public selectedObjects : Array < RenderResult > = [ ] ; public selectedObjectsIDs : Array < number > = [ ] ; /**
     * Total number of results
     */ public totalResults : number = 0 ; /**
     * Current type from the route resolve.
     */ private typeSubject : BehaviorSubject < CmdbType > = new BehaviorSubject < CmdbType > ( undefined ) ; /**
     * Getter for the type definition from the typeSubject.
     */ public get type ( ) : CmdbType { return this . typeSubject . getValue ( ) as CmdbType ; } public tableStateSubject : BehaviorSubject < TableState > = new BehaviorSubject < TableState > ( undefined ) ; public tableStates : Array < TableState > = [ ] ; public get tableState ( ) : TableState { return this . tableStateSubject . getValue ( ) as TableState ; } /**
     * Objects loading flag.
     */ public loading : boolean = false ; /**
     * Render mode for the render elements.
     */ public mode : CmdbMode = CmdbMode . Simple ; /**
     * Default sort filter.
     */ public sort : Sort = { name : 'public_id' , order : SortDirection . DESCENDING } as Sort ; /**
     * Filter search term from table search
     */ public search_term : string ; private private collectionFilterParameter : any [ ] = [ ] ; public selectReset : Array < RenderResult > = [ ] ; public initialVisibleColumns : Array < string > = [ ] ; public columns : Array < Column > ; public readonly initPage : number = 1 ; public page : number = this . initPage ; private readonly initLimit : number = 25 ; public limit : number = this . initLimit ; public formatList : any [ ] = [ ] ; private deleteManyModalRef : NgbModalRef ; constructor ( private router , private route , private objectService , private fileService , private fileSaverService , private toastService , private sidebarService , private modalService , private indexDB , private userSettingsService ) { this . route . data . pipe ( takeUntil ( this . subscriber ) ) . subscribe ( ( data ) => { if ( data . userSetting ) { const userSettingPayloads = ( data . userSetting as UserSetting < TableStatePayload > ) . payloads . find ( payloads => payloads . id === this . id ) ; this . tableStates = userSettingPayloads . tableStates ; this . tableStateSubject . next ( userSettingPayloads . currentState ) ; } else { this . tableStates = [ ] ; this . tableStateSubject . next ( undefined ) ; const statePayload = new TableStatePayload ( this . id , [ ] ) ; const resource = convertResourceURL ( this . router . url . toString ( ) ) ; const userSetting = userSettingsService . createUserSetting < TableStatePayload > ( resource , [ statePayload ] ) ; this . indexDB . addSetting ( userSetting ) ; } this . typeSubject . next ( data . type as CmdbType ) ; } ) ; this . fileService . callFileFormatRoute ( ) . subscribe ( data => { this . formatList = data ; } ) ; } public ngOnInit ( ) { this . typeSubject . asObservable ( ) . pipe ( takeUntil ( this . subscriber ) ) . subscribe ( ( type ) => { this . reload ( type ) ; } ) ; } param type public reload ( type ) { this . resetTable ( ) ; this . setColumns ( type ) ; this . filterBuilder ( this . columns ) ; if ( this . tableState ) { this . page = this . tableState . page ; this . limit = this . tableState . pageSize ; this . sort = this . tableState . sort ; for ( const col of this . columns ) { col . hidden = ! this . tableState . visibleColumns . includes ( col . name ) ; } } if ( this . objectsTableComponent ) { this . objectsTableComponent . columnSearchIconHidden = false ; this . objectsTableComponent . columns = [ ] ; } this . loadObjects ( ) ; } public resetTable ( ) { this . page = this . initPage ; this . limit = this . initLimit ; this . search_term = undefined ; this . selectedObjects = [ ] ; } private setColumns ( type ) { const fields = type . fields || [ ] ; const summaryFields = type . render_meta . summary . fields || [ ] ; const columns = [ { display : 'Active' , name : 'active' , data : 'object_information.active' , searchable : false , sortable : false , type : 'checkbox' , template : this . activeTemplate , cssClasses : [ 'text-center' ] , style : { width : '6rem' } } , { display : 'Public ID' , name : 'public_id' , data : 'object_information.object_id' , type : 'text' , searchable : true , sortable : true } ] as Array < Column > ; for ( const field of fields ) { columns . push ( { display : field . label , name : `fields.${ field . name }` , data : field . name , type : field . type , sortable : true , searchable : true , hidden : ! summaryFields . includes ( field . name ) , render ( data , item , column ) { const renderedField = item . fields . find ( f => f . name === column . data ) ; if ( ! renderedField ) { return { } ; } return { field : renderedField , value : renderedField . value } ; } , template : this . fieldTemplate } as Column ) ; } columns . push ( { display : 'Author' , name : 'author_id' , data : 'object_information.author_name' , type : 'text' , sortable : true , searchable : false , } as Column ) ; columns . push ( { display : 'Creation Time' , name : 'creation_time' , data : 'object_information.creation_time' , type : 'date' , sortable : true , searchable : false , template : this . dateTemplate , } as Column ) ; columns . push ( { display : 'Modification Time' , name : 'last_edit_time' , data : 'object_information.last_edit_time' , type : 'date' , sortable : true , searchable : false , template : this . dateTemplate , render ( data ) { if ( ! data ) { return 'No modifications so far.' ; } return data ; } } as Column ) ; columns . push ( { display : 'Actions' , name : 'actions' , sortable : false , searchable : false , type : 'text' , fixed : true , template : this . actionTemplate , cssClasses : [ 'text-center' ] , style : { width : '6em' } } as unknown as Column ) ; this . initialVisibleColumns = columns . filter ( c => ! c . hidden ) . map ( c => c . name ) ; this . columns = columns ; } /**
     * Load objects from the backend with default filters
     */ public loadObjects ( ) { this . getObjectsFromBackend ( ) ; } private private getObjectsFromBackend ( ) { this . loading = true ; this . selectedObjects = [ ] ; this . selectedObjectsIDs = [ ] ; const params = { filter : this . collectionFilterParameter , limit : this . limit , sort : this . sort . name , order : this . sort . order , page : this . page } ; this . objectService . getObjects ( params ) . pipe ( takeUntil ( this . subscriber ) ) . subscribe ( ( apiResponse ) => { this . results = apiResponse . results as Array < RenderResult > ; this . totalResults = apiResponse . total ; this . loading = false ; } ) ; } param columns Array are taken into account in the query.
      private filterBuilder ( columns ) { this . collectionFilterParameter = [ ] ; this . collectionFilterParameter . push ( { $match : { type_id : this . type . public_id } } ) ; if ( this . search_term ) { const or = [ ] ; for ( const column of columns ) { const regex = { } ; if ( 'checkbox' === column . type ) { try { regex [ 'fields.name' ] = column . name . slice ( 7 ) ; regex [ 'fields.value' ] = Boolean ( JSON . parse ( this . search_term ) ) ; or . push ( regex ) ; } catch ( e ) { } } } this . referenceFieldQuery ( this . collectionFilterParameter ) ; this . addFieldsCreationTime ( this . collectionFilterParameter ) ; this . addFieldsLastEditTime ( this . collectionFilterParameter ) ; this . dateFieldValuesQuery ( this . collectionFilterParameter ) ; this . publicIdQuery ( this . collectionFilterParameter ) ; or . push ( this . elementMatchFields ( this . search_term ) ) ; this . collectionFilterParameter . push ( { $match : { $or : or } } ) ; } } param content param field param input private elementMatchFields ( content , field ? , input = 'references' ) { const value = typeof content === 'boolean' ? content : { $regex : String ( content ) , $options : 'ism' } ; return { [ input ] : { $elemMatch : { name : field , value } } } ; } param property param value private searchRegexProperty ( property , value ) { return { [ property ] : { $regex : String ( value ) , $options : 'ims' } } ; } param query private private referenceFieldQuery ( query ) { if ( query . find ( x => x . hasOwnProperty ( '$lookup' ) || x . hasOwnProperty ( '$project' ) || x . hasOwnProperty ( '$group' ) ) ) { return query ; } return query . push ( { $lookup : { from : 'framework.objects' , localField : 'fields.value' , foreignField : 'public_id' , as : 'data' } } , { $project : { _id : 1 , public_id : 1 , type_id : 1 , active : 1 , author_id : 1 , creation_time : 1 , last_edit_time : 1 , fields : 1 , simple : { $reduce : { input : '$data.fields' , initialValue : [ ] , in : { $setUnion : [ '$$value' , '$$this' ] } } } } } , { $group : { _id : '$_id' , public_id : { $first : '$public_id' } , type_id : { $first : '$type_id' } , active : { $first : '$active' } , author_id : { $first : '$author_id' } , creation_time : { $first : '$creation_time' } , last_edit_time : { $first : '$last_edit_time' } , fields : { $first : '$fields' } , simple : { $first : '$simple' } , } } , { $project : { _id : '$_id' , public_id : 1 , type_id : 1 , active : 1 , author_id : 1 , creation_time : 1 , last_edit_time : 1 , fields : 1 , references : { $concatArrays : [ '$fields' , '$simple' ] } , } } ) ; } param query private private addFieldsCreationTime ( query ) { return query . push ( { $addFields : { creationString : { $dateToString : { format : '%Y-%m-%dT%H:%M:%S.%LZ' , date : '$creation_time' } } } } ) ; } param query private private addFieldsLastEditTime ( query ) { return query . push ( { $addFields : { editString : { $dateToString : { format : '%Y-%m-%dT%H:%M:%S.%LZ' , date : '$last_edit_time' } } } } ) ; } param query param input private private dateFieldValuesQuery ( query , input = 'references' ) { return query . push ( { $addFields : { references : { $map : { input : '$' + input , as : 'new_fields' , in : { $cond : [ { $eq : [ { $type : '$$new_fields.value' } , 'date' ] } , { name : '$$new_fields.name' , value : { $dateToString : { format : '%Y-%m-%dT%H:%M:%S.%LZ' , date : '$$new_fields.value' } } } , { name : '$$new_fields.name' , value : '$$new_fields.value' } ] } } } } } ) ; } private private publicIdQuery ( query ) { return query . push ( { $addFields : { public_id : { $toString : '$public_id' } } } ) ; } private private activeStateQuery ( query ) { return query . push ( { $addFields : { active : { $toString : '$active' } } } ) ; } param page public onPageChange ( page ) { this . page = page ; this . loadObjects ( ) ; } param limit public onPageSizeChange ( limit ) { this . page = this . initPage ; this . limit = limit ; this . loadObjects ( ) ; } param sort public onSortChange ( sort ) { this . sort = sort ; this . loadObjects ( ) ; } param search public onSearchChange ( search ) { this . page = this . initPage ; if ( search ) { this . search_term = search ; } else { this . search_term = undefined ; } const searchableColumns = this . columns . filter ( c => c . searchable ) ; this . filterBuilder ( searchableColumns ) ; this . loadObjects ( ) ; } param changes public onColumnSearchChange ( changes ) { if ( changes . length === 0 && this . objectsTableComponent && ! this . objectsTableComponent . columnSearchIconHidden ) { this . onSearchChange ( this . search_term ) ; } else { this . collectionFilterParameter = [ ] ; this . collectionFilterParameter . push ( { $match : { type_id : this . type . public_id } } ) ; if ( changes . length > 0 ) { const or = [ ] ; for ( const change of changes ) { const and = [ ] ; for ( const column of change ) { const prefix = column . name . slice ( 0 , 6 ) ; const name = 'fields' === prefix ? column . name . slice ( 7 ) : column . name ; switch ( column . type ) { case 'ref' : case 'ref-section-field' : this . referenceFieldQuery ( this . collectionFilterParameter ) ; and . push ( { references : { $elemMatch : { value : { $regex : String ( column . data ) , $options : 'ism' } } } } ) ; break ; case 'date' : if ( 'fields' === prefix ) { this . dateFieldValuesQuery ( this . collectionFilterParameter , 'fields' ) ; or . push ( this . elementMatchFields ( column . data , name ) ) ; } if ( 'creation_time' === name ) { this . addFieldsCreationTime ( this . collectionFilterParameter ) ; and . push ( this . searchRegexProperty ( 'creationString' , column . data ) ) ; } if ( 'last_edit_time' === name ) { this . addFieldsLastEditTime ( this . collectionFilterParameter ) ; and . push ( { editString : { $regex : String ( column . data ) , $options : 'ims' } } ) ; } break ; case 'checkbox' : try { if ( 'active' === name ) { and . push ( { active : column . data } ) ; } else { and . push ( this . elementMatchFields ( Boolean ( JSON . parse ( column . data ) ) , column . name . slice ( 7 ) , 'fields' ) ) ; } } catch ( e ) { } break ; default : console . log ( name ) ; if ( 'public_id' === name ) { this . publicIdQuery ( this . collectionFilterParameter ) ; and . push ( this . searchRegexProperty ( 'public_id' , column . data ) ) ; } else { and . push ( this . elementMatchFields ( column . data , 'fields' === prefix ? name : column . name , 'fields' ) ) ; } break ; } } or . push ( { $and : and } ) ; } this . collectionFilterParameter . push ( { $match : { $or : or } } ) ; } this . page = this . initPage ; this . getObjectsFromBackend ( ) ; } } param selectedItems public onSelectedChange ( selectedItems ) { this . selectedObjects = selectedItems ; this . selectedObjectsIDs = selectedItems . map ( m => m . object_information . object_id ) ; } param state public onStateSelect ( state ) { this . tableStateSubject . next ( state ) ; this . page = this . tableState . page ; this . limit = this . tableState . pageSize ; this . sort = this . tableState . sort ; for ( const col of this . columns ) { col . hidden = ! this . tableState . visibleColumns . includes ( col . name ) ; } this . loadObjects ( ) ; } public onStateReset ( ) { this . tableStateSubject . next ( undefined ) ; this . reload ( this . type ) ; } public exportingFiles ( see ) { const optional = { classname : see . extension , zip : false , metadata : undefined } ; const columns = this . columns . filter ( c => ! c . hidden && ! c . fixed ) ; const properties = [ ] ; const fields = [ ] ; for ( const col of columns ) { const { name } = ( col as any ) ; if ( name && name . startsWith ( 'fields.' ) ) { fields . push ( name . replace ( 'fields.' , '' ) ) ; } else { properties . push ( name ) ; } } optional . metadata = { header : properties , columns : fields } ; const exportAPI = { filter : this . collectionFilterParameter , optional , order : this . sort . order , sort : this . sort . name } ; if ( this . selectedObjectsIDs . length > 0 ) { exportAPI . filter = [ { $match : { public_id : { $in : this . selectedObjectsIDs } } } , ... this . collectionFilterParameter ] ; } this . fileService . callExportRoute ( exportAPI , see . view ) . subscribe ( res => { this . fileSaverService . save ( res . body , new Date ( ) . toISOString ( ) + '.' + see . label ) ; } ) ; } public onObjectDelete ( publicID ) { this . objectService . deleteObject ( publicID ) . pipe ( takeUntil ( this . subscriber ) ) . subscribe ( ( ) => { this . toastService . success ( `Object ${ publicID } was deleted successfully` ) ; this . sidebarService . updateTypeCounter ( this . type . public_id ) ; this . loadObjects ( ) ; } , ( error ) => { this . toastService . error ( `Error while deleting object ${ publicID } | Error: ${ error }` ) ; } ) ; } public onManyObjectDeletes ( ) { if ( this . selectedObjects . length > 0 ) { this . deleteManyModalRef = this . modalService . open ( ObjectsDeleteModalComponent , { size : 'lg' } ) ; this . deleteManyModalRef . result . then ( ( response ) => { if ( response === 'delete' ) { this . objectService . deleteManyObjects ( this . selectedObjectsIDs . toString ( ) ) . pipe ( takeUntil ( this . subscriber ) ) . subscribe ( ( ) => { this . toastService . success ( `Deleted ${ this . selectedObjects . length } objects successfully` ) ; this . sidebarService . updateTypeCounter ( this . type . public_id ) ; this . selectedObjects = [ ] ; this . objectsTableComponent . selectedItems = [ ] ; this . loadObjects ( ) ; } ) ; } } ) ; } } public ngOnDestroy ( ) { this . subscriber . next ( ) ; this . subscriber . complete ( ) ; } }