aggregation
This commit is contained in:
		
							parent
							
								
									7f80ea6330
								
							
						
					
					
						commit
						56e1e3b0fe
					
				| @ -17,145 +17,181 @@ | |||||||
|       </button> |       </button> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|   <ng-container *ngIf="!isCardview"> <!-- GET ALL --> <clr-datagrid [clrDgLoading]="loading" |   <ng-container *ngIf="!isCardview"> <!-- GET ALL --> | ||||||
|       [(clrDgSelected)]="selected"> |     <div style="overflow-x: auto;"> | ||||||
|       <clr-dg-placeholder> |       <clr-datagrid [clrDgLoading]="loading" [(clrDgSelected)]="selected" style="width: max-content; min-width: 100%;"> | ||||||
|         <ng-template #loadingSpinner> |         <clr-dg-placeholder> | ||||||
|           <clr-spinner>Loading ... </clr-spinner> |           <ng-template #loadingSpinner> | ||||||
|         </ng-template> |             <clr-spinner>Loading ... </clr-spinner> | ||||||
|         <div *ngIf="error;else loadingSpinner">{{error}}</div> |           </ng-template> | ||||||
|       </clr-dg-placeholder> |           <div *ngIf="error;else loadingSpinner">{{error}}</div> | ||||||
|  |         </clr-dg-placeholder> | ||||||
| 
 | 
 | ||||||
|       <clr-dg-column [clrDgField]="'name'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Name |         <clr-dg-column [clrDgField]="'name'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Name | ||||||
|         </ng-container></clr-dg-column> |           </ng-container></clr-dg-column> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|       <clr-dg-column [clrDgField]="'url'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url |         <clr-dg-column [clrDgField]="'url'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url | ||||||
|         </ng-container></clr-dg-column> |           </ng-container></clr-dg-column> | ||||||
|  | 
 | ||||||
|  |         <clr-dg-column [clrDgField]="'url_endpoint'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url Endpoint | ||||||
|  |           </ng-container></clr-dg-column> | ||||||
|  | 
 | ||||||
|  |         <clr-dg-column [clrDgField]="'schedule'"> <ng-container *clrDgHideableColumn="{hidden: false}"> schedule | ||||||
|  |           </ng-container></clr-dg-column> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|       <clr-dg-column [clrDgField]="'schedule'"> <ng-container *clrDgHideableColumn="{hidden: false}"> schedule |         <clr-dg-column [clrDgField]="'cron_job'"> <ng-container *clrDgHideableColumn="{hidden: false}"> cron job | ||||||
|         </ng-container></clr-dg-column> |           </ng-container></clr-dg-column> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|       <clr-dg-column [clrDgField]="'cron_job'"> <ng-container *clrDgHideableColumn="{hidden: false}"> cron job |         <clr-dg-column [clrDgField]="'json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> json | ||||||
|         </ng-container></clr-dg-column> |           </ng-container></clr-dg-column> | ||||||
|  | 
 | ||||||
|  |         <!-- Add groupby_json column --> | ||||||
|  |         <clr-dg-column [clrDgField]="'groupby_json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||||
|  |             Group By JSON | ||||||
|  |           </ng-container></clr-dg-column> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|       <clr-dg-column [clrDgField]="'json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> json | 
 | ||||||
|         </ng-container></clr-dg-column> |         <clr-dg-column [clrDgField]="'batch_volume'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Batch Volume | ||||||
|  |           </ng-container></clr-dg-column> | ||||||
|  | 
 | ||||||
|  |         <clr-dg-column [clrDgField]="'sure_connect_id'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||||
|  |             SureConnect | ||||||
|  |           </ng-container></clr-dg-column> | ||||||
|  | 
 | ||||||
|  |         <clr-dg-column [clrDgField]="'calculated_field_json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||||
|  |             Calculated Fields | ||||||
|  |           </ng-container></clr-dg-column> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|       <clr-dg-column [clrDgField]="'url_endpoint'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url Endpoint |  | ||||||
|         </ng-container></clr-dg-column> |  | ||||||
| 
 |  | ||||||
|       <clr-dg-column [clrDgField]="'batch_volume'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Batch Volume |  | ||||||
|         </ng-container></clr-dg-column> |  | ||||||
| 
 |  | ||||||
|       <clr-dg-column [clrDgField]="'sure_connect_id'"> <ng-container *clrDgHideableColumn="{hidden: false}"> SureConnect |  | ||||||
|         </ng-container></clr-dg-column> |  | ||||||
| 
 |  | ||||||
|       <clr-dg-column [clrDgField]="'calculated_field_json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> |  | ||||||
|           Calculated Fields |  | ||||||
|         </ng-container></clr-dg-column> |  | ||||||
| 
 |  | ||||||
|       <!-- who column --> |  | ||||||
|       <clr-dg-column> |  | ||||||
|         <ng-container *clrDgHideableColumn="{hidden: false}"> |  | ||||||
|           <clr-icon shape="bars"></clr-icon> Action |  | ||||||
|         </ng-container></clr-dg-column> |  | ||||||
|       <!-- end --> |  | ||||||
| 
 |  | ||||||
|       <clr-dg-row *clrDgItems="let user of product" [clrDgItem]="user"> |  | ||||||
| 
 |  | ||||||
|         <clr-dg-cell>{{user.name }}</clr-dg-cell> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         <clr-dg-cell (click)="goTourlUrl(user.url)" |  | ||||||
|           style="cursor: pointer; color: rgb(108, 108, 194);">{{user.url}}</clr-dg-cell> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         <clr-dg-cell>{{user.schedule }}</clr-dg-cell> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         <clr-dg-cell>{{user.cron_job }}</clr-dg-cell> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         <clr-dg-cell (click)="goToReplaceStringjson(user.json)" style="cursor: pointer; align-items: center;"><clr-icon |  | ||||||
|             shape="details"></clr-icon></clr-dg-cell> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         <clr-dg-cell> |  | ||||||
|           <div style="display: flex; align-items: center; max-width: 200px;"> |  | ||||||
|             <span |  | ||||||
|               style="margin-right: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-grow: 1;" |  | ||||||
|               title="{{user.url}}" (click)="showFullUrl(user.url)"> |  | ||||||
|               {{user.url | slice:0:30}}{{user.url.length > 30 ? '...' : ''}} |  | ||||||
|             </span> |  | ||||||
|             <button class="btn btn-icon btn-sm" (click)="copyToClipboard(user.url)" title="Copy URL"> |  | ||||||
|               <clr-icon shape="copy-to-clipboard"></clr-icon> |  | ||||||
|             </button> |  | ||||||
|           </div> |  | ||||||
|         </clr-dg-cell> |  | ||||||
|         <clr-dg-cell>{{user.batch_volume}}</clr-dg-cell> |  | ||||||
| 
 |  | ||||||
|         <clr-dg-cell>{{user.sureconnect_name}}</clr-dg-cell> |  | ||||||
| 
 |  | ||||||
|         <clr-dg-cell (click)="goToReplaceStringjson(user.calculated_field_json)" |  | ||||||
|           style="cursor: pointer; align-items: center;"><clr-icon shape="details" |  | ||||||
|             *ngIf="user.calculated_field_json"></clr-icon></clr-dg-cell> |  | ||||||
| 
 | 
 | ||||||
|         <!-- who column --> |         <!-- who column --> | ||||||
|         <clr-dg-cell> |         <clr-dg-column> | ||||||
|           <clr-signpost> |           <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||||
|             <span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success" |             <clr-icon shape="bars"></clr-icon> Action | ||||||
|                 style="color: rgb(0, 130, 236);"></clr-icon></span> |           </ng-container></clr-dg-column> | ||||||
|             <clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen> |         <!-- end --> | ||||||
|               <h5 style="margin-top: 0">Who Column</h5> |  | ||||||
|               <div>Account ID: <code class="clr-code">{{user.accountId}}</code></div> |  | ||||||
|               <div>Created At: <code class="clr-code">{{user.createdAt| date}}</code></div> |  | ||||||
|               <div>Created By: <code class="clr-code">{{user.createdBy}}</code></div> |  | ||||||
|               <div>Updated At: <code class="clr-code">{{user.updatedAt | date}}</code></div> |  | ||||||
|               <div>Updated By: <code class="clr-code">{{user.updatedBy}}</code></div> |  | ||||||
|             </clr-signpost-content> |  | ||||||
|           </clr-signpost> |  | ||||||
| 
 | 
 | ||||||
|           <!-- New JSON Update button --> |         <clr-dg-row *clrDgItems="let user of product" [clrDgItem]="user"> | ||||||
|           <button class="btn btn-icon" (click)="updateJson(user.id)" title="Update JSON"> |  | ||||||
|             <clr-icon shape="refresh"></clr-icon> |  | ||||||
|           </button> |  | ||||||
| 
 | 
 | ||||||
|           <!-- Calculated Field button --> |           <clr-dg-cell>{{user.name }}</clr-dg-cell> | ||||||
|           <button class="btn btn-icon" (click)="fetchAvailableKeys(user)" title="Create Calculated Field"> |  | ||||||
|             <clr-icon shape="calculator"></clr-icon> |  | ||||||
|           </button> |  | ||||||
|         </clr-dg-cell> |  | ||||||
| 
 | 
 | ||||||
|         <!-- who colmn --> |  | ||||||
| 
 | 
 | ||||||
|         <!-- <clr-dg-cell> |           <clr-dg-cell> | ||||||
|   <button class="btn btn-icon" (click)="onEdit(user)"> |             <div style="display: flex; align-items: center; max-width: 200px;"> | ||||||
|     <clr-icon shape="pencil"></clr-icon> |               <span | ||||||
|   </button> |                 style="margin-right: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-grow: 1;" | ||||||
|   <button class="btn btn-icon" (click)="onDelete(user)"> |                 title="{{user.url}}" (click)="showFullUrl(user.url)"> | ||||||
|     <clr-icon shape="trash"></clr-icon> |                 {{user.url | slice:0:30}}{{user.url.length > 30 ? '...' : ''}} | ||||||
|   </button> |               </span> | ||||||
|    |               <button class="btn btn-icon btn-sm" (click)="copyToClipboard(user.url)" title="Copy URL"> | ||||||
| </clr-dg-cell> --> |                 <clr-icon shape="copy-to-clipboard"></clr-icon> | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |           </clr-dg-cell> | ||||||
| 
 | 
 | ||||||
|         <clr-dg-action-overflow> | 
 | ||||||
|           <button class="action-item" (click)="onEdit(user)">Edit</button> |           <clr-dg-cell> | ||||||
|           <button class="action-item" (click)="onDelete(user)">Delete</button> |             <div style="display: flex; align-items: center; max-width: 200px;"> | ||||||
|         </clr-dg-action-overflow> |               <span | ||||||
|       </clr-dg-row> |                 style="margin-right: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex-grow: 1;" | ||||||
|       <clr-dg-footer> |                 title="{{user.url_endpoint}}" (click)="showFullUrl(user.url_endpoint)"> | ||||||
|         <clr-dg-pagination #pagination [clrDgPageSize]="10"> |                 {{user.url | slice:0:30}}{{user.url_endpoint.length > 30 ? '...' : ''}} | ||||||
|           <clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size> |               </span> | ||||||
|           {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} |               <button class="btn btn-icon btn-sm" (click)="copyToClipboard(user.url_endpoint)" title="Copy URL"> | ||||||
|           of {{pagination.totalItems}} users |                 <clr-icon shape="copy-to-clipboard"></clr-icon> | ||||||
|         </clr-dg-pagination> |               </button> | ||||||
|       </clr-dg-footer> |             </div> | ||||||
|     </clr-datagrid> </ng-container> |           </clr-dg-cell> | ||||||
|  | 
 | ||||||
|  |           <clr-dg-cell>{{user.schedule }}</clr-dg-cell> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |           <clr-dg-cell>{{user.cron_job }}</clr-dg-cell> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |           <clr-dg-cell (click)="goToReplaceStringjson(user.json)" | ||||||
|  |             style="cursor: pointer; align-items: center;"><clr-icon shape="details"></clr-icon></clr-dg-cell> | ||||||
|  | 
 | ||||||
|  |           <!-- Add groupby_json cell --> | ||||||
|  |           <clr-dg-cell (click)="goToReplaceStringjson(user.groupby_json)" | ||||||
|  |             style="cursor: pointer; align-items: center;"><clr-icon shape="details" | ||||||
|  |               *ngIf="user.groupby_json"></clr-icon></clr-dg-cell> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |           <clr-dg-cell>{{user.batch_volume}}</clr-dg-cell> | ||||||
|  | 
 | ||||||
|  |           <clr-dg-cell>{{user.sureconnect_name}}</clr-dg-cell> | ||||||
|  | 
 | ||||||
|  |           <clr-dg-cell (click)="goToReplaceStringjson(user.calculated_field_json)" | ||||||
|  |             style="cursor: pointer; align-items: center;"><clr-icon shape="details" | ||||||
|  |               *ngIf="user.calculated_field_json"></clr-icon></clr-dg-cell> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |           <!-- who column --> | ||||||
|  |           <clr-dg-cell> | ||||||
|  |             <div style="display: flex; align-items: center; gap: 1px; flex-wrap: nowrap;"> | ||||||
|  |               <clr-signpost> | ||||||
|  |                 <span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success" | ||||||
|  |                     style="color: rgb(0, 130, 236);"></clr-icon></span> | ||||||
|  |                 <clr-signpost-content [clrPosition]="'left-middle'" *clrIfOpen> | ||||||
|  |                   <h5 style="margin-top: 0">Who Column</h5> | ||||||
|  |                   <div>Account ID: <code class="clr-code">{{user.accountId}}</code></div> | ||||||
|  |                   <div>Created At: <code class="clr-code">{{user.createdAt| date}}</code></div> | ||||||
|  |                   <div>Created By: <code class="clr-code">{{user.createdBy}}</code></div> | ||||||
|  |                   <div>Updated At: <code class="clr-code">{{user.updatedAt | date}}</code></div> | ||||||
|  |                   <div>Updated By: <code class="clr-code">{{user.updatedBy}}</code></div> | ||||||
|  |                 </clr-signpost-content> | ||||||
|  |               </clr-signpost> | ||||||
|  | 
 | ||||||
|  |               <!-- New JSON Update button --> | ||||||
|  |               <button class="btn btn-icon btn-sm" (click)="updateJson(user.id)" title="Update JSON"> | ||||||
|  |                 <clr-icon shape="refresh"></clr-icon> | ||||||
|  |               </button> | ||||||
|  | 
 | ||||||
|  |               <!-- Calculated Field button --> | ||||||
|  |               <button class="btn btn-icon btn-sm" (click)="fetchAvailableKeys(user)" title="Create Calculated Field"> | ||||||
|  |                 <clr-icon shape="calculator"></clr-icon> | ||||||
|  |               </button> | ||||||
|  | 
 | ||||||
|  |               <!-- Group By button --> | ||||||
|  |               <button class="btn btn-icon btn-sm" (click)="openGroupByModal(user)" title="Group By Configuration"> | ||||||
|  |                 <clr-icon shape="group"></clr-icon> | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |           </clr-dg-cell> | ||||||
|  | 
 | ||||||
|  |           <!-- who colmn --> | ||||||
|  | 
 | ||||||
|  |           <!-- <clr-dg-cell> | ||||||
|  |     <button class="btn btn-icon" (click)="onEdit(user)"> | ||||||
|  |       <clr-icon shape="pencil"></clr-icon> | ||||||
|  |     </button> | ||||||
|  |     <button class="btn btn-icon" (click)="onDelete(user)"> | ||||||
|  |       <clr-icon shape="trash"></clr-icon> | ||||||
|  |     </button> | ||||||
|  |      | ||||||
|  |   </clr-dg-cell> --> | ||||||
|  | 
 | ||||||
|  |           <clr-dg-action-overflow> | ||||||
|  |             <button class="action-item" (click)="onEdit(user)">Edit</button> | ||||||
|  |             <button class="action-item" (click)="onDelete(user)">Delete</button> | ||||||
|  |           </clr-dg-action-overflow> | ||||||
|  |         </clr-dg-row> | ||||||
|  |         <clr-dg-footer> | ||||||
|  |           <clr-dg-pagination #pagination [clrDgPageSize]="10"> | ||||||
|  |             <clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size> | ||||||
|  |             {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} | ||||||
|  |             of {{pagination.totalItems}} users | ||||||
|  |           </clr-dg-pagination> | ||||||
|  |         </clr-dg-footer> | ||||||
|  |       </clr-datagrid> | ||||||
|  |     </div> | ||||||
|  |   </ng-container> | ||||||
|   <ng-template #showInfo> |   <ng-template #showInfo> | ||||||
|     <div class="alert alert-info" role="alert"> |     <div class="alert alert-info" role="alert"> | ||||||
|       <div class="alert-items"> |       <div class="alert-items"> | ||||||
| @ -232,7 +268,7 @@ | |||||||
|                       {{afterText(item.fieldtext)}} |                       {{afterText(item.fieldtext)}} | ||||||
|                     </div> |                     </div> | ||||||
| 
 | 
 | ||||||
|                     | 
 | ||||||
|                     <div *ngIf="item.name === 'Line'" class="title-card card-title" |                     <div *ngIf="item.name === 'Line'" class="title-card card-title" | ||||||
|                       [style.text-align]="item.alignment !== '' ? item.alignment : 'left'" |                       [style.text-align]="item.alignment !== '' ? item.alignment : 'left'" | ||||||
|                       [style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'" |                       [style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'" | ||||||
| @ -246,7 +282,7 @@ | |||||||
|                       <hr> |                       <hr> | ||||||
|                     </div> |                     </div> | ||||||
| 
 | 
 | ||||||
|  <div *ngIf="item.name === 'Icon'" class="icon-card" |                     <div *ngIf="item.name === 'Icon'" class="icon-card" | ||||||
|                       [style.text-align]="item.alignment !== '' ? item.alignment : 'left'" |                       [style.text-align]="item.alignment !== '' ? item.alignment : 'left'" | ||||||
|                       [style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'" |                       [style.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'" | ||||||
|                       [style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'" |                       [style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'" | ||||||
| @ -289,7 +325,41 @@ | |||||||
| 
 | 
 | ||||||
| <clr-modal [(clrModalOpen)]="rsModaljson" [clrModalSize]="'xl'" [clrModalStaticBackdrop]="true"> | <clr-modal [(clrModalOpen)]="rsModaljson" [clrModalSize]="'xl'" [clrModalStaticBackdrop]="true"> | ||||||
|   <div class="modal-body"> |   <div class="modal-body"> | ||||||
|     <textarea class="form-control" style="width:100%; height: 400px;" readonly>{{rowSelected}}</textarea> |     <!-- Grid view above JSON --> | ||||||
|  |     <div *ngIf="isJsonData(rowSelected)" class="json-grid-view" style="margin-bottom: 20px;"> | ||||||
|  |       <h4>JSON Data Grid View</h4> | ||||||
|  |       <div style="overflow-x: auto; max-height: 300px; overflow-y: auto;"> | ||||||
|  |         <clr-datagrid style="width: max-content; min-width: 100%;"> | ||||||
|  |           <!-- Generate columns dynamically based on JSON keys --> | ||||||
|  |           <clr-dg-column *ngFor="let header of getJsonHeaders(rowSelected)" style="min-width: 150px;"> | ||||||
|  |             <ng-container *clrDgHideableColumn="{hidden: false}"> | ||||||
|  |               {{header}} | ||||||
|  |             </ng-container> | ||||||
|  |           </clr-dg-column> | ||||||
|  | 
 | ||||||
|  |           <!-- Generate rows dynamically from JSON data --> | ||||||
|  |           <clr-dg-row *ngFor="let item of getJsonData(rowSelected); let i = index" [clrDgItem]="item"> | ||||||
|  |             <clr-dg-cell *ngFor="let header of getJsonHeaders(rowSelected)" style="min-width: 150px;"> | ||||||
|  |               <span [title]="item[header]" | ||||||
|  |                 style="display: inline-block; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> | ||||||
|  |                 {{item[header]}} | ||||||
|  |               </span> | ||||||
|  |             </clr-dg-cell> | ||||||
|  |           </clr-dg-row> | ||||||
|  | 
 | ||||||
|  |           <clr-dg-footer> | ||||||
|  |             <clr-dg-pagination #jsonPagination [clrDgPageSize]="5"> | ||||||
|  |               <clr-dg-page-size [clrPageSizeOptions]="[5, 10, 20]">Items per page</clr-dg-page-size> | ||||||
|  |               {{jsonPagination.firstItem + 1}} - {{jsonPagination.lastItem + 1}} | ||||||
|  |               of {{getJsonData(rowSelected).length}} items | ||||||
|  |             </clr-dg-pagination> | ||||||
|  |           </clr-dg-footer> | ||||||
|  |         </clr-datagrid> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <!-- JSON view below grid --> | ||||||
|  |     <textarea class="form-control" style="width:100%; height: 200px;" readonly>{{rowSelected}}</textarea> | ||||||
|   </div> |   </div> | ||||||
| </clr-modal> | </clr-modal> | ||||||
| 
 | 
 | ||||||
| @ -641,11 +711,12 @@ | |||||||
|                   </select> |                   </select> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="constant-input-container"> |                 <div class="constant-input-container"> | ||||||
|                   <input type="text" class="constant-input" formControlName="constant" placeholder="Or enter constant value" /> |                   <input type="text" class="constant-input" formControlName="constant" | ||||||
|  |                     placeholder="Or enter constant value" /> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div class="remove-btn-container"> |                 <div class="remove-btn-container"> | ||||||
|                   <button type="button" class="btn btn-icon btn-danger remove-field-btn" (click)="removeFieldComponent(i)" |                   <button type="button" class="btn btn-icon btn-danger remove-field-btn" | ||||||
|                     *ngIf="getFieldComponents().length > 1"> |                     (click)="removeFieldComponent(i)" *ngIf="getFieldComponents().length > 1"> | ||||||
|                     <clr-icon shape="trash"></clr-icon> |                     <clr-icon shape="trash"></clr-icon> | ||||||
|                   </button> |                   </button> | ||||||
|                 </div> |                 </div> | ||||||
| @ -681,7 +752,7 @@ | |||||||
|                 <td class="operation-cell">{{ field.operation }}</td> |                 <td class="operation-cell">{{ field.operation }}</td> | ||||||
|                 <td class="expression-cell"> |                 <td class="expression-cell"> | ||||||
|                   <span *ngIf="field.operation === 'complex'">{{ field.complexEquation }}</span> |                   <span *ngIf="field.operation === 'complex'">{{ field.complexEquation }}</span> | ||||||
|                   <span *ngIf="field.operation !== 'complex'"> |                   <span *ngIf="field.operation !== 'complex' && field.type !== 'groupby'"> | ||||||
|                     <span *ngFor="let component of field.fieldComponents; let last = last"> |                     <span *ngFor="let component of field.fieldComponents; let last = last"> | ||||||
|                       <span *ngIf="component.field && !component.isConstant">{{ component.field }}</span> |                       <span *ngIf="component.field && !component.isConstant">{{ component.field }}</span> | ||||||
|                       <span *ngIf="component.isConstant">"{{ component.constant }}"</span> |                       <span *ngIf="component.isConstant">"{{ component.constant }}"</span> | ||||||
| @ -689,9 +760,24 @@ | |||||||
|                       <span *ngIf="!last"> {{ getOperationSymbol(field.operation) }} </span> |                       <span *ngIf="!last"> {{ getOperationSymbol(field.operation) }} </span> | ||||||
|                     </span> |                     </span> | ||||||
|                   </span> |                   </span> | ||||||
|  |                   <span *ngIf="field.type === 'groupby'"> | ||||||
|  |                     GROUP BY: {{ field.groupFields.join(', ') }} | ||||||
|  |                     <div *ngIf="field.aggregations && field.aggregations.length > 0"> | ||||||
|  |                       <small> | ||||||
|  |                         <div *ngFor="let agg of field.aggregations"> | ||||||
|  |                           {{ agg.operation.toUpperCase() }}({{ agg.field }}) | ||||||
|  |                         </div> | ||||||
|  |                       </small> | ||||||
|  |                     </div> | ||||||
|  |                   </span> | ||||||
|                 </td> |                 </td> | ||||||
|                 <td class="actions-cell"> |                 <td> | ||||||
|                   <button class="btn btn-icon btn-danger delete-field-btn" (click)="deleteCalculatedField(field.id)"> |                   <button class="btn btn-icon btn-danger" (click)="deleteCalculatedField(field.id)" | ||||||
|  |                     *ngIf="field.type !== 'groupby'"> | ||||||
|  |                     <clr-icon shape="trash"></clr-icon> | ||||||
|  |                   </button> | ||||||
|  |                   <button class="btn btn-icon btn-danger" (click)="removeGroupByConfig(field.id)" | ||||||
|  |                     *ngIf="field.type === 'groupby'"> | ||||||
|                     <clr-icon shape="trash"></clr-icon> |                     <clr-icon shape="trash"></clr-icon> | ||||||
|                   </button> |                   </button> | ||||||
|                 </td> |                 </td> | ||||||
| @ -732,15 +818,76 @@ | |||||||
|   </div> |   </div> | ||||||
| </clr-modal> | </clr-modal> | ||||||
| 
 | 
 | ||||||
|  | <!-- Group By Modal --> | ||||||
|  | <clr-modal [(clrModalOpen)]="showGroupByModal" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="true"> | ||||||
|  |   <h3 class="modal-title">Group By Configuration</h3> | ||||||
|  |   <div class="modal-body"> | ||||||
|  |     <div class="clr-row"> | ||||||
|  |       <div class="clr-col-12"> | ||||||
|  |         <h4>Group By Fields</h4> | ||||||
|  |         <div class="field-container"> | ||||||
|  |           <span *ngFor="let key of availableKeys" class="field-tag" [class.selected]="isGroupByFieldSelected(key)" | ||||||
|  |             (click)="isGroupByFieldSelected(key) ? removeGroupByField(key) : addGroupByField(key)"> | ||||||
|  |             {{ key }} | ||||||
|  |           </span> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
| 
 | 
 | ||||||
|  |       <div class="clr-col-12" style="margin-top: 20px;"> | ||||||
|  |         <h4>Selected Group By Fields</h4> | ||||||
|  |         <div class="selected-fields-container"> | ||||||
|  |           <span *ngFor="let field of selectedGroupByFields" class="selected-field-tag"> | ||||||
|  |             {{ field }} | ||||||
|  |             <button class="btn btn-icon btn-sm" (click)="removeGroupByField(field)"> | ||||||
|  |               <clr-icon shape="times"></clr-icon> | ||||||
|  |             </button> | ||||||
|  |           </span> | ||||||
|  |           <div *ngIf="selectedGroupByFields.length === 0" class="no-selection"> | ||||||
|  |             No fields selected for grouping | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
| 
 | 
 | ||||||
|  |       <div class="clr-col-12" style="margin-top: 20px;"> | ||||||
|  |         <h4>Aggregation Operations</h4> | ||||||
|  |         <div class="aggregation-container"> | ||||||
|  |           <div *ngFor="let agg of selectedAggregationFields; let i = index" class="aggregation-row"> | ||||||
|  |             <div class="clr-row"> | ||||||
|  |               <div class="clr-col-5"> | ||||||
|  |                 <label>Field</label> | ||||||
|  |                 <select class="clr-select" [(ngModel)]="agg.field"> | ||||||
|  |                   <option value="">Select Field</option> | ||||||
|  |                   <option *ngFor="let key of availableKeys" [value]="key">{{ key }}</option> | ||||||
|  |                 </select> | ||||||
|  |               </div> | ||||||
|  |               <div class="clr-col-5"> | ||||||
|  |                 <label>Operation</label> | ||||||
|  |                 <select class="clr-select" [(ngModel)]="agg.operation"> | ||||||
|  |                   <option *ngFor="let op of aggregationOperations" [value]="op.value">{{ op.label }}</option> | ||||||
|  |                 </select> | ||||||
|  |               </div> | ||||||
|  |               <div class="clr-col-2"> | ||||||
|  |                 <button class="btn btn-icon btn-danger" (click)="removeAggregationField(i)" style="margin-top: 20px;"> | ||||||
|  |                   <clr-icon shape="trash"></clr-icon> | ||||||
|  |                 </button> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
| 
 | 
 | ||||||
| 
 |           <button class="btn btn-sm" (click)="addAggregationField()" style="margin-top: 10px;"> | ||||||
| 
 |             <clr-icon shape="plus"></clr-icon> Add Aggregation | ||||||
| 
 |           </button> | ||||||
| 
 |         </div> | ||||||
| 
 |       </div> | ||||||
| 
 |     </div> | ||||||
| 
 |   </div> | ||||||
|  |   <div class="modal-footer"> | ||||||
|  |     <button type="button" class="btn btn-outline" (click)="showGroupByModal = false">Cancel</button> | ||||||
|  |     <button type="button" class="btn btn-primary" (click)="applyGroupBy()" | ||||||
|  |       [disabled]="selectedGroupByFields.length === 0"> | ||||||
|  |       Apply Group By | ||||||
|  |     </button> | ||||||
|  |   </div> | ||||||
|  | </clr-modal> | ||||||
| 
 | 
 | ||||||
| <!-- htmlpopup --> | <!-- htmlpopup --> | ||||||
| @ -294,4 +294,93 @@ | |||||||
|   .constant-input-container { |   .constant-input-container { | ||||||
|     margin-bottom: 8px; |     margin-bottom: 8px; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /* Group By Modal Styles */ | ||||||
|  | .field-container { | ||||||
|  |   display: flex; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   gap: 8px; | ||||||
|  |   margin-top: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .field-tag { | ||||||
|  |   display: inline-flex; | ||||||
|  |   align-items: center; | ||||||
|  |   background-color: #e3f2fd; | ||||||
|  |   border: 1px solid #bbdefb; | ||||||
|  |   border-radius: 20px; | ||||||
|  |   padding: 6px 12px; | ||||||
|  |   margin: 4px; | ||||||
|  |   font-size: 13px; | ||||||
|  |   font-family: monospace; | ||||||
|  |   cursor: pointer; | ||||||
|  |   transition: all 0.2s; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .field-tag:hover { | ||||||
|  |   background-color: #bbdefb; | ||||||
|  |   transform: translateY(-2px); | ||||||
|  |   box-shadow: 0 2px 4px rgba(0,0,0,0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .field-tag.selected { | ||||||
|  |   background-color: #1976d2; | ||||||
|  |   color: white; | ||||||
|  |   border-color: #1976d2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .selected-fields-container { | ||||||
|  |   min-height: 50px; | ||||||
|  |   padding: 10px; | ||||||
|  |   border: 1px dashed #ccc; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   background-color: #f9f9f9; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .selected-field-tag { | ||||||
|  |   display: inline-flex; | ||||||
|  |   align-items: center; | ||||||
|  |   background-color: #1976d2; | ||||||
|  |   color: white; | ||||||
|  |   border-radius: 20px; | ||||||
|  |   padding: 6px 12px; | ||||||
|  |   margin: 4px; | ||||||
|  |   font-size: 13px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .selected-field-tag .btn-icon { | ||||||
|  |   margin-left: 8px; | ||||||
|  |   padding: 2px; | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .no-selection { | ||||||
|  |   color: #999; | ||||||
|  |   font-style: italic; | ||||||
|  |   padding: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .aggregation-container { | ||||||
|  |   margin-top: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .aggregation-row { | ||||||
|  |   background-color: #f5f5f5; | ||||||
|  |   padding: 15px; | ||||||
|  |   border-radius: 4px; | ||||||
|  |   margin-bottom: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .aggregation-row label { | ||||||
|  |   display: block; | ||||||
|  |   margin-bottom: 5px; | ||||||
|  |   font-weight: 500; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .clr-select { | ||||||
|  |   width: 100%; | ||||||
|  |   padding: 5px; | ||||||
|  |   border: 1px solid #ccc; | ||||||
|  |   border-radius: 4px; | ||||||
|  | } | ||||||
|  | |||||||
| @ -76,6 +76,25 @@ export class Data_lakeComponent implements OnInit { | |||||||
|   currentConstantValue = ''; |   currentConstantValue = ''; | ||||||
|   pendingEquationUpdate = ''; |   pendingEquationUpdate = ''; | ||||||
|    |    | ||||||
|  |   // New properties for group by functionality
 | ||||||
|  |   groupByFields: string[] = []; | ||||||
|  |   selectedGroupByFields: string[] = []; | ||||||
|  |   showGroupByModal = false; | ||||||
|  |    | ||||||
|  |   // Aggregation operations
 | ||||||
|  |   aggregationOperations = [ | ||||||
|  |     { value: 'count', label: 'Count' }, | ||||||
|  |     { value: 'sum', label: 'Sum' }, | ||||||
|  |     { value: 'avg', label: 'Average' }, | ||||||
|  |     { value: 'min', label: 'Minimum' }, | ||||||
|  |     { value: 'max', label: 'Maximum' }, | ||||||
|  |     { value: 'median', label: 'Median' }, | ||||||
|  |     { value: 'mode', label: 'Mode' }, | ||||||
|  |     { value: 'stdev', label: 'Standard Deviation' } | ||||||
|  |   ]; | ||||||
|  |    | ||||||
|  |   selectedAggregationFields: { field: string; operation: string }[] = []; | ||||||
|  |    | ||||||
|   constructor( |   constructor( | ||||||
|     private extensionService: ExtensionService, |     private extensionService: ExtensionService, | ||||||
|     private userInfoService: UserInfoService, |     private userInfoService: UserInfoService, | ||||||
| @ -745,6 +764,7 @@ export class Data_lakeComponent implements OnInit { | |||||||
|       this.ngOnInit(); |       this.ngOnInit(); | ||||||
|     }, 500); |     }, 500); | ||||||
|   } |   } | ||||||
|  |    | ||||||
|   goToAdd(row) { |   goToAdd(row) { | ||||||
|     this.modalAdd = true; |     this.modalAdd = true; | ||||||
|     this.addCronExpression = ''; |     this.addCronExpression = ''; | ||||||
| @ -807,8 +827,10 @@ export class Data_lakeComponent implements OnInit { | |||||||
|           this.product[index].iscalculatedfield = true; |           this.product[index].iscalculatedfield = true; | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         // Close the modal
 |         // Close the modal only when called directly from the UI
 | ||||||
|         this.calculatedFieldModalOpen = false; |         if (this.calculatedFieldModalOpen) { | ||||||
|  |           this.calculatedFieldModalOpen = false; | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|       (error) => { |       (error) => { | ||||||
|         console.error('Error updating calculated fields:', error); |         console.error('Error updating calculated fields:', error); | ||||||
| @ -968,4 +990,195 @@ export class Data_lakeComponent implements OnInit { | |||||||
|     console.log('Validation result:', isValid); |     console.log('Validation result:', isValid); | ||||||
|     console.log('Defined constants:', this.getDefinedConstants()); |     console.log('Defined constants:', this.getDefinedConstants()); | ||||||
|   } |   } | ||||||
|  |    | ||||||
|  |   // Method to open group by modal
 | ||||||
|  |   openGroupByModal(dataLakeItem: any) { | ||||||
|  |     this.selectedDataLakeItem = dataLakeItem; | ||||||
|  |      | ||||||
|  |     // Fetch available keys if not already fetched
 | ||||||
|  |     if (this.availableKeys.length === 0) { | ||||||
|  |       this.fetchAvailableKeys(dataLakeItem); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Initialize selected group by fields as empty array
 | ||||||
|  |     this.selectedGroupByFields = []; | ||||||
|  |     this.selectedAggregationFields = []; | ||||||
|  |     this.showGroupByModal = true; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Method to add a field to group by selection
 | ||||||
|  |   addGroupByField(field: string) { | ||||||
|  |     if (!this.selectedGroupByFields.includes(field)) { | ||||||
|  |       this.selectedGroupByFields.push(field); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Method to remove a field from group by selection
 | ||||||
|  |   removeGroupByField(field: string) { | ||||||
|  |     const index = this.selectedGroupByFields.indexOf(field); | ||||||
|  |     if (index > -1) { | ||||||
|  |       this.selectedGroupByFields.splice(index, 1); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Method to add an aggregation field
 | ||||||
|  |   addAggregationField() { | ||||||
|  |     this.selectedAggregationFields.push({ field: '', operation: 'count' }); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Method to remove an aggregation field
 | ||||||
|  |   removeAggregationField(index: number) { | ||||||
|  |     this.selectedAggregationFields.splice(index, 1); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Method to update aggregation field
 | ||||||
|  |   updateAggregationField(index: number, field: string, operation: string) { | ||||||
|  |     this.selectedAggregationFields[index] = { field, operation }; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Method to apply group by
 | ||||||
|  |   applyGroupBy() { | ||||||
|  |     if (this.selectedGroupByFields.length === 0) { | ||||||
|  |       this.toastr.error('Please select at least one field for grouping'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Validate aggregation fields
 | ||||||
|  |     for (const agg of this.selectedAggregationFields) { | ||||||
|  |       if (!agg.field) { | ||||||
|  |         this.toastr.error('Please select a field for all aggregation operations'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Create group by configuration
 | ||||||
|  |     const groupByConfig = { | ||||||
|  |       id: Date.now(), | ||||||
|  |       type: 'groupby', | ||||||
|  |       groupFields: [...this.selectedGroupByFields], | ||||||
|  |       aggregations: [...this.selectedAggregationFields], | ||||||
|  |       timestamp: new Date().toISOString() | ||||||
|  |     }; | ||||||
|  |      | ||||||
|  |     // Add to calculated fields
 | ||||||
|  |     this.calculatedFields.push(groupByConfig); | ||||||
|  |     this.toastr.success('Group by configuration added successfully'); | ||||||
|  |      | ||||||
|  |     // Update the calculated fields in the backend
 | ||||||
|  |     this.updateCalculatedFieldsAfterGroupBy(); | ||||||
|  |      | ||||||
|  |     this.showGroupByModal = false; | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // New method to update calculated fields after adding group by configuration
 | ||||||
|  |   updateCalculatedFieldsAfterGroupBy() { | ||||||
|  |     if (!this.selectedDataLakeItem || this.calculatedFields.length === 0) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Convert calculated fields to JSON string
 | ||||||
|  |     const calculatedFieldJson = JSON.stringify(this.calculatedFields); | ||||||
|  |      | ||||||
|  |     // Call the service method to update the record
 | ||||||
|  |     this.mainService.updateCalculatedFields( | ||||||
|  |       this.selectedDataLakeItem.id,  | ||||||
|  |       calculatedFieldJson,  | ||||||
|  |       true // iscalculatedfield = true
 | ||||||
|  |     ).subscribe( | ||||||
|  |       (response) => { | ||||||
|  |         console.log('Calculated fields updated successfully:', response); | ||||||
|  |         this.toastr.success('Group by configuration saved successfully'); | ||||||
|  |          | ||||||
|  |         // Update the local data
 | ||||||
|  |         const index = this.product.findIndex(item => item.id === this.selectedDataLakeItem.id); | ||||||
|  |         if (index !== -1) { | ||||||
|  |           this.product[index].calculated_field_json = calculatedFieldJson; | ||||||
|  |           this.product[index].iscalculatedfield = true; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       (error) => { | ||||||
|  |         console.error('Error updating calculated fields:', error); | ||||||
|  |         this.toastr.error('Failed to save group by configuration'); | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Method to remove group by configuration
 | ||||||
|  |   removeGroupByConfig(id: number) { | ||||||
|  |     this.calculatedFields = this.calculatedFields.filter(field => field.id !== id || field.type !== 'groupby'); | ||||||
|  |     this.toastr.success('Group by configuration removed'); | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   // Method to check if a field is selected for group by
 | ||||||
|  |   isGroupByFieldSelected(field: string): boolean { | ||||||
|  |     return this.selectedGroupByFields.includes(field); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Helper method to get the count of calculated fields
 | ||||||
|  |   getCalculatedFieldsCount(calculatedFieldJson: string): number { | ||||||
|  |     try { | ||||||
|  |       const fields = JSON.parse(calculatedFieldJson); | ||||||
|  |       return Array.isArray(fields) ? fields.length : 0; | ||||||
|  |     } catch (e) { | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Helper method to get the count of group by configurations
 | ||||||
|  |   getGroupByFieldsCount(groupByJson: string): number { | ||||||
|  |     try { | ||||||
|  |       const configs = JSON.parse(groupByJson); | ||||||
|  |       return Array.isArray(configs) ? configs.length : 0; | ||||||
|  |     } catch (e) { | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Helper method to check if the selected data is JSON
 | ||||||
|  |   isJsonData(data: any): boolean { | ||||||
|  |     if (!data) return false; | ||||||
|  |      | ||||||
|  |     try { | ||||||
|  |       const parsed = typeof data === 'string' ? JSON.parse(data) : data; | ||||||
|  |       return Array.isArray(parsed) && parsed.length > 0; | ||||||
|  |     } catch (e) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Helper method to get JSON headers for grid columns
 | ||||||
|  |   getJsonHeaders(data: any): string[] { | ||||||
|  |     if (!this.isJsonData(data)) return []; | ||||||
|  |      | ||||||
|  |     try { | ||||||
|  |       const parsed = typeof data === 'string' ? JSON.parse(data) : data; | ||||||
|  |       if (Array.isArray(parsed) && parsed.length > 0) { | ||||||
|  |         // Get all unique keys from the first few items
 | ||||||
|  |         const headers = new Set<string>(); | ||||||
|  |         const sampleItems = parsed.slice(0, 3); // Check first 3 items for headers
 | ||||||
|  |          | ||||||
|  |         sampleItems.forEach(item => { | ||||||
|  |           if (typeof item === 'object' && item !== null) { | ||||||
|  |             Object.keys(item).forEach(key => headers.add(key)); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |          | ||||||
|  |         return Array.from(headers); | ||||||
|  |       } | ||||||
|  |       return []; | ||||||
|  |     } catch (e) { | ||||||
|  |       return []; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Helper method to get JSON data for grid rows
 | ||||||
|  |   getJsonData(data: any): any[] { | ||||||
|  |     if (!this.isJsonData(data)) return []; | ||||||
|  |      | ||||||
|  |     try { | ||||||
|  |       return typeof data === 'string' ? JSON.parse(data) : data; | ||||||
|  |     } catch (e) { | ||||||
|  |       return []; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user