]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
b1d3e369b34f4c5c739a04487c423fe3b2e71391
[ceph-ci.git] /
1 import {
2   Component,
3   EventEmitter,
4   Input,
5   OnChanges,
6   OnInit,
7   Output,
8   ViewChild
9 } from '@angular/core';
10
11 import * as _ from 'lodash';
12
13 import { CellTemplate } from '../../enum/cell-template.enum';
14 import { CdTableColumn } from '../../models/cd-table-column';
15 import { CdDatePipe } from '../../pipes/cd-date.pipe';
16 import { TableComponent } from '../table/table.component';
17
18 class Item {
19   key: string;
20   value: any;
21 }
22
23 /**
24  * Display the given data in a 2 column data table. The left column
25  * shows the 'key' attribute, the right column the 'value' attribute.
26  * The data table has the following characteristics:
27  * - No header and footer is displayed
28  * - The relation of the width for the columns 'key' and 'value' is 1:3
29  * - The 'key' column is displayed in bold text
30  */
31 @Component({
32   selector: 'cd-table-key-value',
33   templateUrl: './table-key-value.component.html',
34   styleUrls: ['./table-key-value.component.scss']
35 })
36 export class TableKeyValueComponent implements OnInit, OnChanges {
37   @ViewChild(TableComponent, { static: true })
38   table: TableComponent;
39
40   @Input()
41   data: any;
42   @Input()
43   autoReload: any = 5000;
44   @Input()
45   renderObjects = false;
46   // Only used if objects are rendered
47   @Input()
48   appendParentKey = true;
49   @Input()
50   hideEmpty = false;
51
52   // If set, the classAddingTpl is used to enable different css for different values
53   @Input()
54   customCss?: { [css: string]: number | string | ((any) => boolean) };
55
56   columns: Array<CdTableColumn> = [];
57   tableData: Item[];
58
59   /**
60    * The function that will be called to update the input data.
61    */
62   @Output()
63   fetchData = new EventEmitter();
64
65   constructor(private datePipe: CdDatePipe) {}
66
67   ngOnInit() {
68     this.columns = [
69       {
70         prop: 'key',
71         flexGrow: 1,
72         cellTransformation: CellTemplate.bold
73       },
74       {
75         prop: 'value',
76         flexGrow: 3
77       }
78     ];
79     if (this.customCss) {
80       this.columns[1].cellTransformation = CellTemplate.classAdding;
81     }
82     // We need to subscribe the 'fetchData' event here and not in the
83     // HTML template, otherwise the data table will display the loading
84     // indicator infinitely if data is only bound via '[data]="xyz"'.
85     // See for 'loadingIndicator' in 'TableComponent::ngOnInit()'.
86     if (this.fetchData.observers.length > 0) {
87       this.table.fetchData.subscribe(() => {
88         // Forward event triggered by the 'cd-table' data table.
89         this.fetchData.emit();
90       });
91     }
92     this.useData();
93   }
94
95   ngOnChanges() {
96     this.useData();
97   }
98
99   useData() {
100     if (!this.data) {
101       return; // Wait for data
102     }
103     this.tableData = this._makePairs(this.data);
104   }
105
106   _makePairs(data: any): Item[] {
107     let temp = [];
108     if (!data) {
109       return; // Wait for data
110     } else if (_.isArray(data)) {
111       temp = this._makePairsFromArray(data);
112     } else if (_.isObject(data)) {
113       temp = this._makePairsFromObject(data);
114     } else {
115       throw new Error('Wrong data format');
116     }
117     temp = temp.map((v) => this._convertValue(v)).filter((o) => o); // Filters out undefined
118     return _.sortBy(this.renderObjects ? this.insertFlattenObjects(temp) : temp, 'key');
119   }
120
121   _makePairsFromArray(data: any[]): Item[] {
122     let temp = [];
123     const first = data[0];
124     if (_.isArray(first)) {
125       if (first.length === 2) {
126         temp = data.map((a) => ({
127           key: a[0],
128           value: a[1]
129         }));
130       } else {
131         throw new Error('Wrong array format: [string, any][]');
132       }
133     } else if (_.isObject(first)) {
134       if (_.has(first, 'key') && _.has(first, 'value')) {
135         temp = [...data];
136       } else {
137         temp = data.reduce(
138           (previous: any[], item) => previous.concat(this._makePairsFromObject(item)),
139           temp
140         );
141       }
142     }
143     return temp;
144   }
145
146   _makePairsFromObject(data: object): Item[] {
147     return Object.keys(data).map((k) => ({
148       key: k,
149       value: data[k]
150     }));
151   }
152
153   private insertFlattenObjects(temp: Item[]): any[] {
154     return _.flattenDeep(
155       temp.map((item) => {
156         const value = item.value;
157         const isObject = _.isObject(value);
158         if (!isObject || _.isEmpty(value)) {
159           if (isObject) {
160             item.value = '';
161           }
162           return item;
163         }
164         return this.splitItemIntoItems(item);
165       })
166     );
167   }
168
169   /**
170    * Split item into items will call _makePairs inside _makePairs (recursion), in oder to split
171    * the object item up into items as planned.
172    */
173   private splitItemIntoItems(v: { key: string; value: object }): Item[] {
174     return this._makePairs(v.value).map((item) => {
175       if (this.appendParentKey) {
176         item.key = v.key + ' ' + item.key;
177       }
178       return item;
179     });
180   }
181
182   _convertValue(v: Item): Item {
183     if (_.isArray(v.value)) {
184       if (_.isEmpty(v.value) && this.hideEmpty) {
185         return;
186       }
187       v.value = v.value.map((item) => (_.isObject(item) ? JSON.stringify(item) : item)).join(', ');
188     } else if (_.isObject(v.value)) {
189       if ((this.hideEmpty && _.isEmpty(v.value)) || !this.renderObjects) {
190         return;
191       }
192     } else if (_.isString(v.value)) {
193       if (v.value === '' && this.hideEmpty) {
194         return;
195       }
196       if (this.isDate(v.value)) {
197         v.value = this.datePipe.transform(v.value) || v.value;
198       }
199     }
200
201     return v;
202   }
203
204   private isDate(s) {
205     const sep = '[ -:.TZ]';
206     const n = '\\d{2}' + sep;
207     //                            year     -    m - d - h : m : s . someRest  Z (if UTC)
208     return s.match(new RegExp('^\\d{4}' + sep + n + n + n + n + n + '\\d*' + 'Z?$'));
209   }
210 }