import {
ConditionAdvanced,
ExpressionCondition,
JSON_PATH_REGEX,
OPERATORS,
PFE_AND,
PFE_EXPRESSION,
PFE_OR,
PageId,
PageNavigationConfiguration,
PfeConditionsNormalizeUtils,
PfeConditionsService,
PfeConfigurationService,
PfeNavigationService,
PfeRulesEvaluator,
PfeStateService,
PfeUserInputState,
PfeUtilService,
} from '@allianz/ngx-pfe';
import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import jsonpath from 'jsonpath';
import { debounceTime, filter } from 'rxjs/operators';
const DEVTOOLSURLPARAM = 'devtools';
const EXAMPLE_EXPRESSION = '$.lastVisitedPage';
const EXAMPLE_EXPRESSION_CONDITION: ExpressionCondition[] = [
{ expression: '{$.pfeVisitedPages[:1]} === {$.lastVisitedPage}' },
{ value1Expression: '$.lastVisitedPage', operator: '==', value2Expression: '$.pfeVisitedPages[:1]' },
];
const EXAMPLE_CONDITION_ADVANCED: ConditionAdvanced = {
operator: OPERATORS.AND,
conditions: [
{ value1Expression: '$.lastVisitedPage', operator: '==', value2Expression: '$.pfeHistory[:1]' },
{ value1Expression: 5, operator: '==', value2Expression: 4 },
],
};
/**
* @export
*/
@Component({
selector: 'pfe-dev-tools',
templateUrl: './pfe-dev-tools.component.html',
styleUrls: ['./pfe-dev-tools.component.scss'],
})
export class DevToolsComponent implements OnInit, AfterViewInit {
@Input() devToolsActive = false;
@Input() beautifyJson = false;
json: Record<string, unknown> | undefined;
staleState: Record<string, unknown> | undefined;
isBeautifyJson: boolean | undefined;
jsonPathData: string | undefined;
jsonPathExpression: string | undefined;
jsonPathConditions: string | undefined;
jsonPathResult: string | unknown;
jsonQueryResult: string | undefined;
jsonValueResult: string | undefined;
conditionsResult: boolean | unknown;
jsonStateData: string | undefined;
conditionEvaluationLogs: string | undefined;
pages: PageNavigationConfiguration[] | undefined;
errorPageId: PageId | undefined;
fGroup!: UntypedFormGroup;
jsonPathError = false;
conditionParseError = false;
hideJsonPathMessage = false;
hideConditionMessage = false;
constructor(
public pfeStateService: PfeStateService,
private pfeConfigurationService: PfeConfigurationService,
private pfeNavigationService: PfeNavigationService,
private pfeUtilService: PfeUtilService,
private cdRef: ChangeDetectorRef,
private pfeConditionsService: PfeConditionsService
) {
this.buildForm();
}
get exampleExpression() {
return EXAMPLE_EXPRESSION;
}
get exampleExpressionCondition() {
return JSON.stringify(EXAMPLE_EXPRESSION_CONDITION, null, 2);
}
get exampleConditionAdvanced() {
return JSON.stringify(EXAMPLE_CONDITION_ADVANCED, null, 2);
}
ngOnInit(): void {
const urlParam = this.pfeUtilService.getURLParam(DEVTOOLSURLPARAM);
if (this.devToolsActive === true || (urlParam && urlParam === 'true')) {
this.devToolsActive = true;
this.pfeConfigurationService.getConfig().then((config) => {
this.pages = config.navConfiguration.pages;
this.errorPageId = config.navConfiguration.errorPageID;
});
}
this.pfeStateService.pfeUserInputState$.pipe(debounceTime(500)).subscribe((value) => {
this.json = value;
});
this.copyCurrentState();
}
ngAfterViewInit() {
this.cdRef.detectChanges();
}
buildForm() {
this.fGroup = new UntypedFormGroup({
selectPage: new UntypedFormControl(),
});
this.fGroup.controls.selectPage.valueChanges.pipe(filter((value) => value !== null)).subscribe((value) => this.goToPage(value));
}
goToPage(pageID: string) {
console.log(`Navigating directly to page: ${pageID}`);
this.fGroup.controls.selectPage.reset(null);
this.pfeNavigationService.navigate(pageID);
}
storeOnServer() {
this.pfeStateService.storeState();
}
restoreFromServer() {
this.pfeStateService.automaticallyRestoreState();
}
restoreFromInputField() {
if (this.jsonStateData) {
const state: PfeUserInputState = JSON.parse(this.jsonStateData);
this.pfeStateService.restoreState(state);
}
}
resetState() {
this.pfeStateService.resetState();
}
copyCurrentState() {
const state = this.pfeStateService.getFullState();
this.jsonPathData = JSON.stringify(state);
this.parseJsonPathData();
}
evaluateExpression() {
this.jsonPathResult = '';
this.jsonQueryResult = '';
this.jsonValueResult = '';
try {
if (this.jsonPathData && this.jsonPathExpression) {
const parsedJSON = JSON.parse(this.jsonPathData);
const safeJsonPathExpression = this.jsonPathExpression.replace('[]$', '$');
this.jsonPathResult = JSON.stringify(jsonpath.nodes(parsedJSON, safeJsonPathExpression), null, 2);
this.jsonQueryResult = JSON.stringify(jsonpath.query(parsedJSON, safeJsonPathExpression), null, 2);
this.jsonValueResult = JSON.stringify(PfeUtilService.getValueOrList(parsedJSON, this.jsonPathExpression, true), null, 2);
}
} catch (error) {
this.jsonPathResult = error;
}
}
evaluateCondition() {
try {
if (this.jsonPathData && this.jsonPathConditions) {
const parsedJSON = JSON.parse(this.jsonPathData);
this.conditionsResult = this.pfeConditionsService.evaluateConditions(JSON.parse(this.jsonPathConditions), parsedJSON);
this.logConditionEvaluation(parsedJSON);
}
} catch (error) {
this.conditionsResult = error;
}
}
logConditionEvaluation(parsedJSON: Record<string, unknown>) {
this.conditionEvaluationLogs = '';
if (this.jsonPathData && this.jsonPathConditions) {
const normalizedConditions = PfeConditionsNormalizeUtils.normalizeConditions(JSON.parse(this.jsonPathConditions));
normalizedConditions.forEach((condition, index) => {
// ConditionAdvanced
if (condition.type === PFE_AND || condition.type === PFE_OR) {
const conditionAdvanced = condition as unknown as ConditionAdvanced;
this.conditionEvaluationLogs += `ConditionAdvanced ${condition.type}:\n\n`;
this.conditionEvaluationLogs += '----------------------------------------------------------------\n\n';
const evaluatedValues = conditionAdvanced.conditions.map((expressionCondition, conditionAdvancedIndex) => {
const values = this.logConditionWithOperator(expressionCondition, parsedJSON, conditionAdvancedIndex);
this.conditionEvaluationLogs += '----------------------------------------------------------------\n\n';
return values;
});
const logicalOperator = condition.type === PFE_AND ? ' AND ' : ' OR ';
const conditionsResult = this.pfeConditionsService.evaluateConditions(condition, parsedJSON);
this.conditionEvaluationLogs += `${evaluatedValues.join(logicalOperator)} => ${conditionsResult} \n\n`;
}
if (condition.type === PFE_EXPRESSION) {
const expressionCondition = condition as ExpressionCondition;
// ExpressionCondition with "expression"
if (typeof expressionCondition?.expression == 'string') {
this.conditionEvaluationLogs += `Condition #${index}:\n`;
const replacer = PfeRulesEvaluator.getReplacer(parsedJSON as PfeUserInputState);
const jsonPathExpressions = expressionCondition.expression.match(JSON_PATH_REGEX)?.map((match) => match.slice(1, -1)) || [];
this.conditionEvaluationLogs += jsonPathExpressions
.map((jsonPathExpression: string) => ` - "${jsonPathExpression}" evaluates to ${replacer(jsonPathExpression)}\n`)
.join('');
const conditionsResult = this.pfeConditionsService.evaluateConditions(expressionCondition, parsedJSON);
const evaluatedStatement = expressionCondition.expression.replace(JSON_PATH_REGEX, replacer);
this.conditionEvaluationLogs += `\n ${evaluatedStatement} => ${conditionsResult}\n\n`;
}
// ExpressionCondition with "value1Expression", "value2Expression" and "operator"
else {
this.logConditionWithOperator(expressionCondition, parsedJSON, index);
}
this.conditionEvaluationLogs += '----------------------------------------------------------------\n\n';
}
});
}
}
logConditionWithOperator(expressionCondition: ExpressionCondition, parsedJSON: Record<string, unknown>, conditionIndex: number) {
this.conditionEvaluationLogs += `Condition #${conditionIndex}:\n`;
if (expressionCondition.value1Expression && expressionCondition.value2Expression && expressionCondition.operator) {
const { value1Expression, value2Expression, operator } = expressionCondition;
let evaluatedValue1Expression = value1Expression;
let evaluatedValue2Expression = value2Expression;
if (typeof value1Expression === 'string') {
evaluatedValue1Expression = `"${PfeUtilService.getValueOrList(parsedJSON, value1Expression, true)}"`;
this.conditionEvaluationLogs += ` - "${value1Expression}" evaluates to ${evaluatedValue1Expression}\n`;
}
if (typeof value2Expression === 'string') {
evaluatedValue2Expression = `"${PfeUtilService.getValueOrList(parsedJSON, value2Expression, true)}"`;
this.conditionEvaluationLogs += ` - "${value2Expression}" evaluates to ${evaluatedValue2Expression}\n`;
}
const conditionsResult = this.pfeConditionsService.evaluateConditions(expressionCondition, parsedJSON);
this.conditionEvaluationLogs += `\n ${evaluatedValue1Expression} ${operator} ${evaluatedValue2Expression} => ${conditionsResult}\n\n`;
return conditionsResult;
}
}
parseConditions() {
try {
if (this.jsonPathConditions) {
this.jsonPathConditions = JSON.stringify(JSON.parse(this.jsonPathConditions), null, 2);
this.conditionParseError = false;
}
} catch (e) {
this.conditionParseError = true;
}
}
parseJsonPathData() {
try {
if (this.jsonPathData) {
this.jsonPathData = JSON.stringify(JSON.parse(this.jsonPathData), null, 2);
this.jsonPathError = false;
}
} catch (e) {
this.jsonPathError = true;
}
}
loadExampleExpression() {
this.jsonPathExpression = this.exampleExpression;
}
loadExampleExpressionCondition() {
this.jsonPathConditions = this.exampleExpressionCondition;
}
loadExampleConditionAdvanced() {
this.jsonPathConditions = this.exampleConditionAdvanced;
}
}
<div nxLayout="grid nogutters" class="dev-tools-box" *ngIf="devToolsActive">
<h1 nxHeadline="subsection-medium">Dev Tools</h1>
<nx-tab-group appearance="expert">
<nx-tab label="General">
<div nxRow>
<div nxCol="9,12,6,5">
<h2 class="headline">State Storage</h2>
<button nxButton="primary small" (click)="storeOnServer()" style="margin-right: 8px">Store</button>
<button nxButton="secondary small" (click)="restoreFromServer()">Restore</button>
</div>
<div nxCol="9,12,6,6">
<h2 class="headline">Directly Jump to Page</h2>
<form [formGroup]="fGroup">
<label for="select-page">Select Page ID: </label>
<select formControlName="selectPage" name="select-page" id="cars">
<option *ngFor="let page of pages" [value]="page.pageId">{{ page.pageId }}</option>
<option *ngIf="errorPageId" [value]="errorPageId">{{ errorPageId }}</option>
</select>
</form>
</div>
</div>
<div nxRow>
<div nxCol="9,12,12,9">
<h2 class="headline">Current data model</h2>
<input type="checkbox" id="beautify-json-toggle" [(ngModel)]="isBeautifyJson" />
<label for="beautify-json-toggle">Beautify JSON</label><br />
<pre *ngIf="!isBeautifyJson">{{ json | json }}</pre>
<ngx-json-viewer *ngIf="isBeautifyJson" [json]="json"></ngx-json-viewer>
<form>
<label class="headline" for="json-state-data">Restore state from JSON:</label><br />
<textarea [(ngModel)]="jsonStateData" id="json-state-data" name="jsonStateData" class="form-control"></textarea>
<p>
<button nxButton="primary small" (click)="restoreFromInputField()">Restore</button>
</p>
<p>
<button nxButton="primary small" (click)="resetState()">Reset State</button>
</p>
</form>
</div>
</div>
</nx-tab>
<nx-tab label="JSONPath Expressions">
<h2 class="headline">Custom State</h2>
<form>
<div nxRow>
<div nxCol="12,12,12,12">
<label for="jsonpath-state-data">Json data:</label><br />
<textarea (blur)="parseJsonPathData()" [(ngModel)]="jsonPathData" id="jsonpath-state-data"
name="jsonPathStateData" class="form-control"></textarea><br />
<nx-error *ngIf="jsonPathError" appearance="text">Invalid JSON</nx-error>
</div>
</div>
<p>
<button nxButton="primary small" (click)="copyCurrentState()">Load current app state</button>
</p>
<h2 class="headline nx-margin-top-m">JSONPath Expressions</h2>
<nx-message *ngIf="!hideConditionMessage"
context="info"
closable="true"
(close)="hideConditionMessage = true" context="info">Take a look at the
<nx-link><a target="_blank"
href="https://ngx-pfe.frameworks.allianz.io/assets/docs/additional-documentation/state-storage.html#state-expressions">PFE
documentation</a></nx-link>
to learn more about
<nx-link><a target="_blank" href="https://goessner.net/articles/JsonPath/">JSONPath</a></nx-link>
.
</nx-message>
<label for="json-path-expression">Expression:</label><br />
<input [placeholder]="exampleExpression" [(ngModel)]="jsonPathExpression" id="json-path-expression"
name="jsonPathExpression" />
<p>
<button nxButton="primary small" (click)="evaluateExpression()">Evaluate expression</button>
<button nxButton="secondary small" (click)="loadExampleExpression()">Load example</button>
</p>
<div nxRow>
<div nxCol="12,12,12,12">
<span>Value result:</span>
<textarea readonly class="code-box" name="jsonValueResult">{{ jsonValueResult }}</textarea>
</div>
<details nxCol="12,12,12,12">
<summary>Query & path result (for debugging):</summary>
<div nxRow>
<div nxCol="12,12,12,6">
<span>Query result:</span>
<textarea readonly class="code-box" name="jsonQueryResult">{{ jsonQueryResult }}</textarea>
</div>
<div nxCol="12,12,12,6">
<span>Path result:</span>
<textarea readonly class="code-box" name="jsonPathResult">{{ jsonPathResult }}</textarea>
</div>
</div>
</details>
</div>
<div nxRow>
<div nxCol="12,12,12,12">
<form>
<h2 class="headline nx-margin-top-m">ExpressionCondition Evaluation</h2>
<nx-message
*ngIf="!hideJsonPathMessage"
context="info"
closable="true"
(close)="hideJsonPathMessage = true" context="info">Take a look at the PFE documentation to learn more
about
<nx-link><a target="_blank"
href="https://ngx-pfe.frameworks.allianz.io/assets/docs/additional-documentation/flow-and-backend-integration/pfe-conditions.html">PFE
Conditions</a></nx-link>
in general,
<nx-link><a target="_blank"
href="https://ngx-pfe.frameworks.allianz.io/assets/docs/interfaces/ExpressionCondition.html">ExpressionCondition</a>
</nx-link>
and
<nx-link><a target="_blank"
href="https://ngx-pfe.frameworks.allianz.io/assets/docs/interfaces/ConditionAdvanced.html">ConditionAdvanced</a>
</nx-link>
</nx-message>
<label for="json-path-conditions">Conditions (ExpressionCondition[] | ConditionAdvanced)</label><br />
<textarea
[(ngModel)]="jsonPathConditions"
(blur)="parseConditions()"
id="json-path-conditions"
name="jsonPathConditions"
></textarea>
<nx-error *ngIf="conditionParseError" appearance="text">Invalid JSON</nx-error>
<p>
<button nxButton="primary small" (click)="evaluateCondition()">Evaluate conditions</button>
<button nxButton="secondary small" (click)="loadExampleExpressionCondition()">Load ExpressionCondition[]
example
</button>
<button nxButton="secondary small" (click)="loadExampleConditionAdvanced()">Load ConditionAdvanced
example
</button>
</p>
<span>Result of conditions:</span>
<textarea readonly class="code-box" name="conditionsResult">{{ conditionsResult }}</textarea>
<details>
<summary>Logs from the evaluation of the condition (for debugging):</summary>
<textarea readonly class="code-box">{{ conditionEvaluationLogs }}</textarea>
</details>
</form>
</div>
</div>
</form>
</nx-tab>
<nx-tab label="Actions">
<pfe-dev-tools-actions />
</nx-tab>
</nx-tab-group>
</div>