import { Component, Inject, OnInit, OnChanges, OnDestroy, SimpleChanges, ChangeDetectorRef, CUSTOM_ELEMENTS_SCHEMA, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatDividerModule } from '@angular/material/divider';
import { BreakpointObserver } from '@angular/cdk/layout';
import { I18nService } from '../../../services/i18n.service';
import { SocketService } from '../../../services/socket.service';
import { CommonService } from '../../../services/common.service';
import { JsonViewDialogService } from '../../../dialogs/json-view-dialog.service';
import { KeyNewOrSetDialogService } from '../../../dialogs/key-new-or-set-dialog.service';
import { MainCommandService } from '../../../services/main-command.service';
import { RedisStateService } from '../../../services/redis-state.service';
import { SettingsService } from '../../../services/settings.service';
import { KeyTypeBase } from './key-type-base';
import { P3xrAccordionComponent } from '../../../components/p3xr-accordion.component';
import { P3xrButtonComponent } from '../../../components/p3xr-button.component';
@Component({
selector: 'p3xr-key-probabilistic',
standalone: true,
imports: [
CommonModule, FormsModule,
MatButtonModule, MatIconModule, MatTooltipModule,
MatFormFieldModule, MatInputModule,
MatListModule, MatDividerModule,
P3xrAccordionComponent, P3xrButtonComponent,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './key-probabilistic.component.html',
encapsulation: ViewEncapsulation.None,
})
export class KeyProbabilisticComponent extends KeyTypeBase implements OnInit, OnChanges, OnDestroy {
infoItems: Array<{ key: string; value: any }> = [];
topkItems: Array<{ item: string; count: number }> = [];
itemInput = '';
incrementInput = 1;
quantileInput = 0.5;
autoRefresh = false;
private autoRefreshInterval: any = null;
readonly = false;
constructor(
@Inject(I18nService) i18n: I18nService,
@Inject(SocketService) socket: SocketService,
@Inject(CommonService) common: CommonService,
@Inject(JsonViewDialogService) jsonViewDialog: JsonViewDialogService,
@Inject(KeyNewOrSetDialogService) keyNewOrSetDialog: KeyNewOrSetDialogService,
@Inject(BreakpointObserver) breakpointObserver: BreakpointObserver,
@Inject(MainCommandService) cmd: MainCommandService,
@Inject(ChangeDetectorRef) cdr: ChangeDetectorRef,
@Inject(RedisStateService) redisState: RedisStateService,
@Inject(SettingsService) settingsService: SettingsService,
) {
super(i18n, socket, common, jsonViewDialog, keyNewOrSetDialog, breakpointObserver, cmd, cdr, redisState, settingsService);
}
get strings() {
return this.i18n.strings;
}
get type(): string {
return this.p3xrResponse?.type || '';
}
ngOnInit() {
this.readonly = this.redisState.connection()?.readonly === true;
this.parseInfo();
if (this.type === 'topk') {
this.loadTopkList();
}
}
ngOnChanges(changes: SimpleChanges) {
if (changes['p3xrValue']) {
this.parseInfo();
}
}
ngOnDestroy() {
this.stopAutoRefresh();
this.destroyBase();
}
toggleAutoRefresh(): void {
this.autoRefresh = !this.autoRefresh;
if (this.autoRefresh) {
this.startAutoRefresh();
} else {
this.stopAutoRefresh();
}
this.cdr.markForCheck();
}
private startAutoRefresh(): void {
this.stopAutoRefresh();
this.autoRefreshInterval = setInterval(() => {
this.refresh();
}, 10000);
}
private stopAutoRefresh(): void {
if (this.autoRefreshInterval) {
clearInterval(this.autoRefreshInterval);
this.autoRefreshInterval = null;
}
}
private parseInfo() {
this.infoItems = [];
try {
const val = this.p3xrValue;
let info: any;
if (typeof val === 'object' && val !== null && !ArrayBuffer.isView(val)) {
info = val;
} else if (typeof val === 'string') {
info = JSON.parse(val);
} else if (ArrayBuffer.isView(val)) {
info = JSON.parse(new TextDecoder().decode(val as any));
}
if (info && typeof info === 'object') {
this.infoItems = Object.entries(info).map(([key, value]) => ({ key, value }));
}
} catch {
// ignore parse errors
}
}
async addItem() {
if (!this.itemInput.trim()) return;
try {
await this.socket.request({
action: 'probabilistic/add',
payload: {
key: this.p3xrKey,
type: this.type,
item: this.itemInput.trim(),
increment: this.incrementInput,
},
});
this.common.toast(this.strings()?.page?.key?.probabilistic?.addedSuccessfully);
this.itemInput = '';
this.refresh();
} catch (e: any) {
this.common.toast(e.message || 'Error');
}
this.cdr.markForCheck();
}
async checkItem() {
if (!this.itemInput.trim()) return;
try {
const response = await this.socket.request({
action: 'probabilistic/check',
payload: {
key: this.p3xrKey,
type: this.type,
item: this.itemInput.trim(),
},
});
const strings = this.strings();
const exists = (response as any).result === 1;
this.common.toast(`"${this.itemInput}" — ${exists
? (strings?.page?.key?.probabilistic?.exists)
: (strings?.page?.key?.probabilistic?.doesNotExist)}`);
} catch (e: any) {
this.common.toast(e.message || 'Error');
}
this.cdr.markForCheck();
}
async deleteItem() {
if (!this.itemInput.trim()) return;
try {
await this.common.confirm({
message: this.strings()?.confirm?.delete,
});
await this.socket.request({
action: 'probabilistic/delete',
payload: {
key: this.p3xrKey,
type: this.type,
item: this.itemInput.trim(),
},
});
this.common.toast(this.strings()?.page?.key?.probabilistic?.deletedSuccessfully);
this.itemInput = '';
this.refresh();
} catch (e: any) {
if (e?.message) this.common.toast(e.message || 'Error');
}
this.cdr.markForCheck();
}
async queryItem() {
if (!this.itemInput.trim()) return;
try {
const response = await this.socket.request({
action: 'probabilistic/check',
payload: {
key: this.p3xrKey,
type: this.type,
item: this.itemInput.trim(),
},
});
const count = Array.isArray((response as any).result) ? (response as any).result[0] : (response as any).result;
this.common.toast(`"${this.itemInput}" — ${this.strings()?.page?.key?.probabilistic?.topkCount}: ${count}`);
} catch (e: any) {
this.common.toast(e.message || 'Error');
}
this.cdr.markForCheck();
}
async queryQuantile() {
try {
const response = await this.socket.request({
action: 'probabilistic/check',
payload: {
key: this.p3xrKey,
type: this.type,
quantile: this.quantileInput,
},
});
const strings = this.strings();
const result = Array.isArray((response as any).result) ? (response as any).result[0] : (response as any).result;
this.common.toast(`${strings?.page?.key?.probabilistic?.quantile} ${this.quantileInput} = ${result}`);
} catch (e: any) {
this.common.toast(e.message || 'Error');
}
this.cdr.markForCheck();
}
async resetTdigest() {
try {
await this.common.confirm({
message: this.strings()?.page?.key?.probabilistic?.resetConfirm,
});
await this.socket.request({
action: 'probabilistic/delete',
payload: {
key: this.p3xrKey,
type: 'tdigest',
},
});
this.common.toast('Reset');
this.refresh();
} catch (e: any) {
if (e?.message) this.common.toast(e.message || 'Error');
}
this.cdr.markForCheck();
}
async loadTopkList() {
try {
const response = await this.socket.request({
action: 'probabilistic/check',
payload: {
key: this.p3xrKey,
type: 'topk',
},
});
this.topkItems = (response as any).result || [];
} catch {
this.topkItems = [];
}
this.cdr.markForCheck();
}
refresh() {
this.cmd.refreshKey$.next();
if (this.type === 'topk') {
this.loadTopkList();
}
}
}