aggregation
This commit is contained in:
parent
7f80ea6330
commit
56e1e3b0fe
@ -17,8 +17,9 @@
|
|||||||
</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-datagrid [clrDgLoading]="loading" [(clrDgSelected)]="selected" style="width: max-content; min-width: 100%;">
|
||||||
<clr-dg-placeholder>
|
<clr-dg-placeholder>
|
||||||
<ng-template #loadingSpinner>
|
<ng-template #loadingSpinner>
|
||||||
<clr-spinner>Loading ... </clr-spinner>
|
<clr-spinner>Loading ... </clr-spinner>
|
||||||
@ -33,6 +34,8 @@
|
|||||||
<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
|
<clr-dg-column [clrDgField]="'schedule'"> <ng-container *clrDgHideableColumn="{hidden: false}"> schedule
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
@ -45,20 +48,26 @@
|
|||||||
<clr-dg-column [clrDgField]="'json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> json
|
<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]="'url_endpoint'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url Endpoint
|
<clr-dg-column [clrDgField]="'groupby_json'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
Group By JSON
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<clr-dg-column [clrDgField]="'batch_volume'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Batch Volume
|
<clr-dg-column [clrDgField]="'batch_volume'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Batch Volume
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column [clrDgField]="'sure_connect_id'"> <ng-container *clrDgHideableColumn="{hidden: false}"> SureConnect
|
<clr-dg-column [clrDgField]="'sure_connect_id'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
|
SureConnect
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-column [clrDgField]="'calculated_field_json'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
<clr-dg-column [clrDgField]="'calculated_field_json'"> <ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
Calculated Fields
|
Calculated Fields
|
||||||
</ng-container></clr-dg-column>
|
</ng-container></clr-dg-column>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- who column -->
|
<!-- who column -->
|
||||||
<clr-dg-column>
|
<clr-dg-column>
|
||||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||||
@ -71,20 +80,6 @@
|
|||||||
<clr-dg-cell>{{user.name }}</clr-dg-cell>
|
<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>
|
<clr-dg-cell>
|
||||||
<div style="display: flex; align-items: center; max-width: 200px;">
|
<div style="display: flex; align-items: center; max-width: 200px;">
|
||||||
<span
|
<span
|
||||||
@ -97,6 +92,36 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</clr-dg-cell>
|
</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_endpoint}}" (click)="showFullUrl(user.url_endpoint)">
|
||||||
|
{{user.url | slice:0:30}}{{user.url_endpoint.length > 30 ? '...' : ''}}
|
||||||
|
</span>
|
||||||
|
<button class="btn btn-icon btn-sm" (click)="copyToClipboard(user.url_endpoint)" title="Copy URL">
|
||||||
|
<clr-icon shape="copy-to-clipboard"></clr-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</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.batch_volume}}</clr-dg-cell>
|
||||||
|
|
||||||
<clr-dg-cell>{{user.sureconnect_name}}</clr-dg-cell>
|
<clr-dg-cell>{{user.sureconnect_name}}</clr-dg-cell>
|
||||||
@ -105,8 +130,11 @@
|
|||||||
style="cursor: pointer; align-items: center;"><clr-icon shape="details"
|
style="cursor: pointer; align-items: center;"><clr-icon shape="details"
|
||||||
*ngIf="user.calculated_field_json"></clr-icon></clr-dg-cell>
|
*ngIf="user.calculated_field_json"></clr-icon></clr-dg-cell>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- who column -->
|
<!-- who column -->
|
||||||
<clr-dg-cell>
|
<clr-dg-cell>
|
||||||
|
<div style="display: flex; align-items: center; gap: 1px; flex-wrap: nowrap;">
|
||||||
<clr-signpost>
|
<clr-signpost>
|
||||||
<span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success"
|
<span style="cursor: pointer;" clrSignpostTrigger><clr-icon shape="help" class="success"
|
||||||
style="color: rgb(0, 130, 236);"></clr-icon></span>
|
style="color: rgb(0, 130, 236);"></clr-icon></span>
|
||||||
@ -121,14 +149,20 @@
|
|||||||
</clr-signpost>
|
</clr-signpost>
|
||||||
|
|
||||||
<!-- New JSON Update button -->
|
<!-- New JSON Update button -->
|
||||||
<button class="btn btn-icon" (click)="updateJson(user.id)" title="Update JSON">
|
<button class="btn btn-icon btn-sm" (click)="updateJson(user.id)" title="Update JSON">
|
||||||
<clr-icon shape="refresh"></clr-icon>
|
<clr-icon shape="refresh"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Calculated Field button -->
|
<!-- Calculated Field button -->
|
||||||
<button class="btn btn-icon" (click)="fetchAvailableKeys(user)" title="Create Calculated Field">
|
<button class="btn btn-icon btn-sm" (click)="fetchAvailableKeys(user)" title="Create Calculated Field">
|
||||||
<clr-icon shape="calculator"></clr-icon>
|
<clr-icon shape="calculator"></clr-icon>
|
||||||
</button>
|
</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>
|
</clr-dg-cell>
|
||||||
|
|
||||||
<!-- who colmn -->
|
<!-- who colmn -->
|
||||||
@ -155,7 +189,9 @@
|
|||||||
of {{pagination.totalItems}} users
|
of {{pagination.totalItems}} users
|
||||||
</clr-dg-pagination>
|
</clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
</clr-datagrid> </ng-container>
|
</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">
|
||||||
@ -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 -->
|
||||||
@ -295,3 +295,92 @@
|
|||||||
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
|
||||||
|
if (this.calculatedFieldModalOpen) {
|
||||||
this.calculatedFieldModalOpen = false;
|
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