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