added new box for rules logger poc

This commit is contained in:
maverickstuder 2024-10-16 13:01:11 +02:00 committed by Dan Percic
parent df64a73adc
commit d4435752ac
5 changed files with 259 additions and 88 deletions

View File

@ -1,89 +1,127 @@
<div class="header-container">
<div [translate]="translations[type()]['title']" class="heading-l"></div>
</div>
<div class="flex" style="height: 100%">
<ngx-monaco-editor (init)="onCodeEditorInit($event)" [(ngModel)]="codeEditorText" [options]="editorOptions"></ngx-monaco-editor>
<div [class.collapsed]="collapsed()" class="right-container flex-column">
<div class="collapsed-wrapper">
<ng-container *ngTemplateOutlet="collapsible; context: { action: 'expand', tooltip: ('copilot' | translate) }"></ng-container>
<div class="all-caps-label" translate="dossier-details.title"></div>
<div class="rules-screen-container">
<!-- Main content area -->
<div class="main-content">
<div class="header-container">
<div [translate]="translations[type()]['title']" class="heading-l"></div>
</div>
<div class="header-wrapper flex mt-8">
<div class="heading-xl flex-1" id="dossierDetailsDossierName">Copilot</div>
<ng-container *ngTemplateOutlet="collapsible; context: { action: 'collapse', tooltip: ('copilot' | translate) }"></ng-container>
</div>
<div class="flex" style="height: 100%">
<ngx-monaco-editor (init)="onCodeEditorInit($event)" [(ngModel)]="codeEditorText" [options]="editorOptions"></ngx-monaco-editor>
<div class="mt-24">
@for (comment of responses$ | async; track comment) {
<div class="comment">
<div class="comment-details-wrapper">
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
{{ comment.date | date: 'sophisticatedDate' }}
</div>
<!-- <div class="comment-actions">-->
<!-- </div>-->
</div>
<div>{{ comment.text }}</div>
<div [class.collapsed]="collapsed()" class="right-container flex-column">
<div class="collapsed-wrapper">
<ng-container
*ngTemplateOutlet="collapsible; context: { action: 'expand', tooltip: ('copilot' | translate) }"
></ng-container>
<div class="all-caps-label" translate="dossier-details.title"></div>
</div>
}
<iqser-input-with-action
(action)="add($event)"
[placeholder]="'comments.add-comment' | translate"
autocomplete="off"
icon="iqser:collapse"
width="full"
></iqser-input-with-action>
<div class="header-wrapper flex mt-8">
<div class="heading-xl flex-1" id="dossierDetailsDossierName">Copilot</div>
<ng-container
*ngTemplateOutlet="collapsible; context: { action: 'collapse', tooltip: ('copilot' | translate) }"
></ng-container>
</div>
<div class="mt-24">
@for (comment of responses$ | async; track comment) {
<div class="comment">
<div class="comment-details-wrapper">
<div [matTooltipPosition]="'above'" [matTooltip]="comment.date | date: 'exactDate'" class="small-label">
{{ comment.date | date: 'sophisticatedDate' }}
</div>
<!-- <div class="comment-actions">-->
<!-- </div>-->
</div>
<div>{{ comment.text }}</div>
</div>
}
<iqser-input-with-action
(action)="add($event)"
[placeholder]="'comments.add-comment' | translate"
autocomplete="off"
icon="iqser:collapse"
width="full"
></iqser-input-with-action>
</div>
</div>
</div>
<ng-template #collapsible let-action="action" let-tooltip="tooltip">
<iqser-circle-button
(action)="collapsed.set(!collapsed())"
[icon]="'iqser:' + action"
[tooltipPosition]="IqserTooltipPositions.before"
[tooltip]="tooltip"
></iqser-circle-button>
</ng-template>
<div *ngIf="changed && permissionsService.canEditRules() && !isLeaving" class="changes-box">
<div (click)="goToErrors()" *ngIf="numberOfErrors() || numberOfWarnings()" class="errors">
<span>
<mat-icon *ngIf="numberOfErrors()" [svgIcon]="'iqser:alert-circle'" class="icon"></mat-icon>
<div class="found-errors">
<span
*ngIf="numberOfErrors()"
[translateParams]="{ errors: numberOfErrors() }"
[translate]="translations[type()]['errors-found']"
>
</span>
<span
*ngIf="numberOfWarnings()"
[class.only-warning]="!numberOfErrors()"
[translateParams]="{ warnings: numberOfWarnings() }"
[translate]="translations[type()]['warnings-found']"
class="warning"
></span>
</div>
</span>
<mat-icon [svgIcon]="'iqser:expand'" class="icon face-up"></mat-icon>
</div>
<div class="actions">
<iqser-icon-button
(action)="save()"
[label]="translations[type()]['save-changes'] | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
></iqser-icon-button>
<div (click)="revert()" [translate]="translations[type()]['revert-changes']" class="all-caps-label cancel"></div>
</div>
</div>
</div>
<div class="rules-logger" [class.collapsed]="isLogCollapsed">
<div class="logger-header" (click)="toggleLogCollapse()">
<h3>Rules Log</h3>
<button class="collapse-button">
<!--todo: add proper icon -->
<mat-icon>{{ isLogCollapsed ? 'x' : 'x' }}</mat-icon>
</button>
</div>
<div class="logger-messages" *ngIf="!isLogCollapsed">
<div *ngFor="let msg of messages" class="log-entry">
<div class="log-header">
<span class="log-level">{{ msg.logLevel }}</span>
<span class="log-time">{{ msg.parsedTimeStamp | date: 'short' }}</span>
</div>
<div class="log-message">
{{ msg.message }}
</div>
<div class="log-details">
<!--todo: add translation -->
<div><strong>Tenant ID:</strong> {{ msg.tenantId }}</div>
<div><strong>File ID:</strong> {{ msg.fileId }}</div>
<div><strong>Dossier ID:</strong> {{ msg.dossierId }}</div>
<div><strong>Rule Version:</strong> {{ msg.ruleVersion }}</div>
<div><strong>Analysis Number:</strong> {{ msg.analysisNumber }}</div>
<div><strong>Timestamp:</strong> {{ msg.parsedTimeStamp }}</div>
</div>
</div>
</div>
</div>
</div>
<ng-template #collapsible let-action="action" let-tooltip="tooltip">
<iqser-circle-button
(action)="collapsed.set(!collapsed())"
[icon]="'iqser:' + action"
[tooltipPosition]="IqserTooltipPositions.before"
[tooltip]="tooltip"
></iqser-circle-button>
</ng-template>
<div *ngIf="changed && permissionsService.canEditRules() && !isLeaving" class="changes-box">
<div (click)="goToErrors()" *ngIf="numberOfErrors() || numberOfWarnings()" class="errors">
<span>
<mat-icon *ngIf="numberOfErrors()" [svgIcon]="'iqser:alert-circle'" class="icon"></mat-icon>
<div class="found-errors">
<span
*ngIf="numberOfErrors()"
[translateParams]="{ errors: numberOfErrors() }"
[translate]="translations[type()]['errors-found']"
>
</span>
<span
*ngIf="numberOfWarnings()"
[class.only-warning]="!numberOfErrors()"
[translateParams]="{ warnings: numberOfWarnings() }"
[translate]="translations[type()]['warnings-found']"
class="warning"
></span>
</div>
</span>
<mat-icon [svgIcon]="'iqser:expand'" class="icon face-up"></mat-icon>
</div>
<div class="actions">
<iqser-icon-button
(action)="save()"
[label]="translations[type()]['save-changes'] | translate"
[type]="iconButtonTypes.primary"
icon="iqser:check"
></iqser-icon-button>
<div (click)="revert()" [translate]="translations[type()]['revert-changes']" class="all-caps-label cancel"></div>
</div>
</div>
<redaction-rules-logger></redaction-rules-logger>

View File

@ -96,3 +96,94 @@ ngx-monaco-editor {
width: 100%;
}
}
.rules-screen-container {
display: flex;
flex-direction: column;
height: 100vh; /* Adjust based on your layout */
.main-content {
flex: 1;
overflow: auto;
}
.rules-logger {
flex-shrink: 0;
transition: max-height 0.3s ease;
overflow: hidden;
border-top: 1px solid #ccc;
background-color: #fafafa;
&.collapsed {
max-height: 50px; /* Height of the header when collapsed */
}
&:not(.collapsed) {
max-height: 300px; /* Maximum height when expanded */
}
.logger-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
cursor: pointer;
h3 {
margin: 0;
}
.collapse-button {
background: none;
border: none;
cursor: pointer;
outline: none;
mat-icon {
font-size: 24px;
}
}
}
.logger-messages {
overflow-y: auto;
height: calc(100% - 50px); /* Subtract header height */
padding: 10px;
.log-entry {
padding: 10px;
border-bottom: 1px solid #eee;
.log-header {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
.log-level {
font-weight: bold;
color: #007bff;
}
.log-time {
font-size: 0.9em;
color: #666;
}
}
.log-message {
margin-bottom: 5px;
font-size: 1em;
}
.log-details {
font-size: 0.9em;
color: #666;
div {
margin-bottom: 2px;
}
}
}
}
}
}

View File

@ -24,7 +24,8 @@ import { rulesScreenTranslations } from '../../../translations/rules-screen-tran
import ICodeEditor = monaco.editor.ICodeEditor;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
import { RulesLoggerComponent } from '@components/rules-logger/rules-logger.component';
import { RulesLoggerService } from '@services/rules-logger.service';
import { ScrollingModule } from '@angular/cdk/scrolling';
interface SyntaxError {
line: number;
@ -39,6 +40,19 @@ interface UploadResponse {
deprecatedWarnings: SyntaxError[];
}
interface RulesLogMessage {
tenantId: string;
fileId: string;
dossierId: string;
dossierTemplateId: string;
ruleVersion: number;
analysisNumber: number;
timeStamp: string;
logLevel: string;
message: string;
parsedTimeStamp?: Date;
}
const RULE_VALIDATION_TIMEOUT = 2000;
@Component({
@ -61,7 +75,7 @@ const RULE_VALIDATION_TIMEOUT = 2000;
NamePipe,
NgForOf,
MatTooltip,
RulesLoggerComponent,
ScrollingModule,
],
})
export default class RulesScreenComponent implements OnInit, ComponentCanDeactivate {
@ -71,6 +85,7 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
readonly #errors = signal<SyntaxError[]>([]);
#ruleValidationTimeout: number = null;
readonly #copilotService = inject(CopilotService);
readonly #rulesLoggerService = inject(RulesLoggerService);
readonly #currentUser = getCurrentUser();
protected readonly collapsed = signal(true);
protected readonly IqserTooltipPositions = IqserTooltipPositions;
@ -88,10 +103,12 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
initialLines: string[] = [];
currentLines: string[] = [];
isLeaving = false;
messages: RulesLogMessage[] = [];
readonly type = input.required<IRules['ruleFileType']>();
readonly numberOfErrors = computed(() => this.#errors().filter(e => !e.warning).length);
readonly numberOfWarnings = computed(() => this.#errors().filter(e => e.warning).length);
readonly responses$ = new BehaviorSubject<{ text: string; date: string }[]>([]);
isLogCollapsed = true;
constructor(
readonly permissionsService: PermissionsService,
@ -114,6 +131,15 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
destination: '/app/rules-copilot',
body: JSON.stringify({ prompts: ['manageradmin'] }),
});
this.#rulesLoggerService
.listen<RulesLogMessage>('/topic/' + tenant + '/rule-log-events')
.pipe(takeUntilDestroyed())
.subscribe(message => {
message.parsedTimeStamp = this.parseTimestamp(message.timeStamp);
this.messages.push(message);
this._changeDetectorRef.detectChanges();
});
}
set isLeavingPage(isLeaving: boolean) {
@ -352,4 +378,23 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv
() => this._loadingService.stop(),
);
}
parseTimestamp(timestamp: string): Date {
let adjustedTimestamp = timestamp.replace('Z', '');
const dotIndex = adjustedTimestamp.indexOf('.');
if (dotIndex !== -1) {
adjustedTimestamp = adjustedTimestamp.substring(0, dotIndex);
}
return new Date(adjustedTimestamp);
}
trackByMessageId(index: number, msg: RulesLogMessage): string {
return msg.timeStamp; // Assuming timeStamp is unique
}
toggleLogCollapse() {
this.isLogCollapsed = !this.isLogCollapsed;
}
}

View File

@ -8,5 +8,3 @@
></ngx-monaco-editor>
<ngx-monaco-diff-editor (init)="onDiffEditorInit($event)" *ngIf="showDiffEditor" [options]="editorOptions"></ngx-monaco-diff-editor>
<redaction-rules-logger></redaction-rules-logger>

View File

@ -7,14 +7,13 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { List, OnChange } from '@iqser/common-ui/lib/utils';
import { MonacoEditorModule, MonacoStandaloneCodeEditor, MonacoStandaloneDiffEditor } from '@materia-ui/ngx-monaco-editor';
import { FormsModule } from '@angular/forms';
import { NgIf } from '@angular/common';
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
import IDiffEditor = monaco.editor.IDiffEditor;
import ICodeEditor = monaco.editor.ICodeEditor;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
import ILineChange = monaco.editor.ILineChange;
import IEditorMouseEvent = monaco.editor.IEditorMouseEvent;
import { RulesLoggerComponent } from '@components/rules-logger/rules-logger.component';
import { NgIf } from '@angular/common';
const MIN_WORD_LENGTH = 2;
const lineChangeToDecoration = ({ originalEndLineNumber, originalStartLineNumber }: ILineChange) =>
@ -33,7 +32,7 @@ const notZero = (lineChange: ILineChange) => lineChange.originalEndLineNumber !=
templateUrl: './editor.component.html',
styleUrls: ['./editor.component.scss'],
standalone: true,
imports: [MonacoEditorModule, FormsModule, NgIf, RulesLoggerComponent],
imports: [MonacoEditorModule, FormsModule, NgIf],
})
export class EditorComponent implements OnInit, OnChanges {
@Input() showDiffEditor = false;