aggregation
This commit is contained in:
@@ -17,145 +17,181 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="!isCardview"> <!-- GET ALL --> <clr-datagrid [clrDgLoading]="loading"
|
||||
[(clrDgSelected)]="selected">
|
||||
<clr-dg-placeholder>
|
||||
<ng-template #loadingSpinner>
|
||||
<clr-spinner>Loading ... </clr-spinner>
|
||||
</ng-template>
|
||||
<div *ngIf="error;else loadingSpinner">{{error}}</div>
|
||||
</clr-dg-placeholder>
|
||||
<ng-container *ngIf="!isCardview"> <!-- GET ALL -->
|
||||
<div style="overflow-x: auto;">
|
||||
<clr-datagrid [clrDgLoading]="loading" [(clrDgSelected)]="selected" style="width: max-content; min-width: 100%;">
|
||||
<clr-dg-placeholder>
|
||||
<ng-template #loadingSpinner>
|
||||
<clr-spinner>Loading ... </clr-spinner>
|
||||
</ng-template>
|
||||
<div *ngIf="error;else loadingSpinner">{{error}}</div>
|
||||
</clr-dg-placeholder>
|
||||
|
||||
<clr-dg-column [clrDgField]="'name'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Name
|
||||
</ng-container></clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'name'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Name
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
|
||||
<clr-dg-column [clrDgField]="'url'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url
|
||||
</ng-container></clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'url'"> <ng-container *clrDgHideableColumn="{hidden: false}"> Url
|
||||
</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
|
||||
</ng-container></clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'cron_job'"> <ng-container *clrDgHideableColumn="{hidden: false}"> cron job
|
||||
</ng-container></clr-dg-column>
|
||||
|
||||
|
||||
<clr-dg-column [clrDgField]="'cron_job'"> <ng-container *clrDgHideableColumn="{hidden: false}"> cron job
|
||||
</ng-container></clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'json'"> <ng-container *clrDgHideableColumn="{hidden: false}"> json
|
||||
</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 -->
|
||||
<clr-dg-cell>
|
||||
<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>
|
||||
<clr-dg-column>
|
||||
<ng-container *clrDgHideableColumn="{hidden: false}">
|
||||
<clr-icon shape="bars"></clr-icon> Action
|
||||
</ng-container></clr-dg-column>
|
||||
<!-- end -->
|
||||
|
||||
<!-- New JSON Update button -->
|
||||
<button class="btn btn-icon" (click)="updateJson(user.id)" title="Update JSON">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</button>
|
||||
<clr-dg-row *clrDgItems="let user of product" [clrDgItem]="user">
|
||||
|
||||
<!-- Calculated Field button -->
|
||||
<button class="btn btn-icon" (click)="fetchAvailableKeys(user)" title="Create Calculated Field">
|
||||
<clr-icon shape="calculator"></clr-icon>
|
||||
</button>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{user.name }}</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>
|
||||
<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> -->
|
||||
|
||||
<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> </ng-container>
|
||||
<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.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>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<div class="alert-items">
|
||||
@@ -246,7 +282,7 @@
|
||||
<hr>
|
||||
</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.line-height]="item.textlineheight !== '' ? item.textlineheight : '1'"
|
||||
[style.font-family]="item.fontName !== '' ? item.fontName : 'Metropolis'"
|
||||
@@ -289,7 +325,41 @@
|
||||
|
||||
<clr-modal [(clrModalOpen)]="rsModaljson" [clrModalSize]="'xl'" [clrModalStaticBackdrop]="true">
|
||||
<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>
|
||||
</clr-modal>
|
||||
|
||||
@@ -641,11 +711,12 @@
|
||||
</select>
|
||||
</div>
|
||||
<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 class="remove-btn-container">
|
||||
<button type="button" class="btn btn-icon btn-danger remove-field-btn" (click)="removeFieldComponent(i)"
|
||||
*ngIf="getFieldComponents().length > 1">
|
||||
<button type="button" class="btn btn-icon btn-danger remove-field-btn"
|
||||
(click)="removeFieldComponent(i)" *ngIf="getFieldComponents().length > 1">
|
||||
<clr-icon shape="trash"></clr-icon>
|
||||
</button>
|
||||
</div>
|
||||
@@ -681,7 +752,7 @@
|
||||
<td class="operation-cell">{{ field.operation }}</td>
|
||||
<td class="expression-cell">
|
||||
<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 *ngIf="component.field && !component.isConstant">{{ component.field }}</span>
|
||||
<span *ngIf="component.isConstant">"{{ component.constant }}"</span>
|
||||
@@ -689,9 +760,24 @@
|
||||
<span *ngIf="!last"> {{ getOperationSymbol(field.operation) }} </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 class="actions-cell">
|
||||
<button class="btn btn-icon btn-danger delete-field-btn" (click)="deleteCalculatedField(field.id)">
|
||||
<td>
|
||||
<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>
|
||||
</button>
|
||||
</td>
|
||||
@@ -732,15 +818,76 @@
|
||||
</div>
|
||||
</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 -->
|
||||
@@ -295,3 +295,92 @@
|
||||
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 = '';
|
||||
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(
|
||||
private extensionService: ExtensionService,
|
||||
private userInfoService: UserInfoService,
|
||||
@@ -745,6 +764,7 @@ export class Data_lakeComponent implements OnInit {
|
||||
this.ngOnInit();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
goToAdd(row) {
|
||||
this.modalAdd = true;
|
||||
this.addCronExpression = '';
|
||||
@@ -807,8 +827,10 @@ export class Data_lakeComponent implements OnInit {
|
||||
this.product[index].iscalculatedfield = true;
|
||||
}
|
||||
|
||||
// Close the modal
|
||||
this.calculatedFieldModalOpen = false;
|
||||
// Close the modal only when called directly from the UI
|
||||
if (this.calculatedFieldModalOpen) {
|
||||
this.calculatedFieldModalOpen = false;
|
||||
}
|
||||
},
|
||||
(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('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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user