From 43e03404c3c2b77de7bd7db22999a4f24878152c Mon Sep 17 00:00:00 2001 From: Dmitriy Borzenko Date: Fri, 18 Oct 2024 21:26:15 +0300 Subject: [PATCH] fixed PR comments --- .../access-tokens-tab.component.ts | 16 +-- src/app/common/app.helpers.ts | 15 +++ .../data-access-panel.component.ts | 16 +-- .../query-explainer.component.html | 83 +++++++------- .../query-explainer.component.ts | 91 +++++++++------ .../query-explainer.service.ts | 6 +- .../query-explainer/query-explainer.types.ts | 105 ++++++++++++------ .../metadata-block/block.service.ts | 4 +- 8 files changed, 192 insertions(+), 144 deletions(-) diff --git a/src/app/auth/settings/tabs/access-tokens-tab/access-tokens-tab.component.ts b/src/app/auth/settings/tabs/access-tokens-tab/access-tokens-tab.component.ts index ab63314b..c402848f 100644 --- a/src/app/auth/settings/tabs/access-tokens-tab/access-tokens-tab.component.ts +++ b/src/app/auth/settings/tabs/access-tokens-tab/access-tokens-tab.component.ts @@ -13,7 +13,7 @@ import { MatTableDataSource } from "@angular/material/table"; import { AccountSettingsTabs, TokenCreateStep } from "../../account-settings.constants"; import { AbstractControl, FormBuilder, FormGroup, Validators } from "@angular/forms"; import { ModalService } from "src/app/components/modal/modal.service"; -import { promiseWithCatch, requireValue } from "src/app/common/app.helpers"; +import { changeCopyIcon, promiseWithCatch, requireValue } from "src/app/common/app.helpers"; import { Clipboard } from "@angular/cdk/clipboard"; import AppValues from "src/app/common/app.values"; import { AccessTokenService } from "src/app/services/access-token.service"; @@ -151,19 +151,7 @@ export class AccessTokensTabComponent extends BaseComponent implements OnInit { /* istanbul ignore next */ public copyToClipboard(event: MouseEvent, text: string): void { this.clipboard.copy(text); - if (event.currentTarget !== null) { - const currentElement: HTMLButtonElement = event.currentTarget as HTMLButtonElement; - const currentElementChildren: HTMLCollectionOf = - currentElement.children as HTMLCollectionOf; - setTimeout(() => { - currentElementChildren[0].style.display = "inline-block"; - currentElementChildren[1].style.display = "none"; - currentElement.classList.remove("clipboard-btn--success"); - }, AppValues.LONG_DELAY_MS); - currentElementChildren[0].style.display = "none"; - currentElementChildren[1].style.display = "inline-block"; - currentElement.classList.add("clipboard-btn--success"); - } + changeCopyIcon(event); } private updateTable(page: number): void { diff --git a/src/app/common/app.helpers.ts b/src/app/common/app.helpers.ts index 4c49593c..7ee5f77b 100644 --- a/src/app/common/app.helpers.ts +++ b/src/app/common/app.helpers.ts @@ -86,3 +86,18 @@ export function convertSecondsToHumanReadableFormat(seconds: number): string { export function capitalizeString(value: string): string { return value[0].toUpperCase() + value.slice(1).toLowerCase(); } + +export function changeCopyIcon(event: MouseEvent): void { + if (event.currentTarget !== null) { + const htmlButtonElement: HTMLButtonElement = event.currentTarget as HTMLButtonElement; + const iconsPair: HTMLCollectionOf = htmlButtonElement.children as HTMLCollectionOf; + setTimeout(() => { + iconsPair[0].style.display = "inline-block"; + iconsPair[1].style.display = "none"; + htmlButtonElement.classList.remove("clipboard-btn--success"); + }, AppValues.LONG_DELAY_MS); + iconsPair[0].style.display = "none"; + iconsPair[1].style.display = "inline-block"; + htmlButtonElement.classList.add("clipboard-btn--success"); + } +} diff --git a/src/app/components/data-access-panel/data-access-panel.component.ts b/src/app/components/data-access-panel/data-access-panel.component.ts index 30d5537f..c303dc21 100644 --- a/src/app/components/data-access-panel/data-access-panel.component.ts +++ b/src/app/components/data-access-panel/data-access-panel.component.ts @@ -2,9 +2,9 @@ import { MaybeUndefined } from "./../../common/app.types"; import { ProtocolsService } from "./../../services/protocols.service"; import { ChangeDetectionStrategy, Component, inject, Input, OnInit } from "@angular/core"; import { DatasetBasicsFragment, DatasetEndpoints } from "src/app/api/kamu.graphql.interface"; -import AppValues from "src/app/common/app.values"; import { Clipboard } from "@angular/cdk/clipboard"; import { Observable } from "rxjs"; +import { changeCopyIcon } from "src/app/common/app.helpers"; @Component({ selector: "app-data-access-panel", @@ -25,19 +25,7 @@ export class DataAccessPanelComponent implements OnInit { public copyToClipboard(event: MouseEvent, text: string): void { this.clipboard.copy(text); - if (event.currentTarget !== null) { - const currentElement: HTMLButtonElement = event.currentTarget as HTMLButtonElement; - const currentElementChildren: HTMLCollectionOf = - currentElement.children as HTMLCollectionOf; - setTimeout(() => { - currentElementChildren[0].style.display = "inline-block"; - currentElementChildren[1].style.display = "none"; - currentElement.classList.remove("clipboard-btn--success"); - }, AppValues.LONG_DELAY_MS); - currentElementChildren[0].style.display = "none"; - currentElementChildren[1].style.display = "inline-block"; - currentElement.classList.add("clipboard-btn--success"); - } + changeCopyIcon(event); } private initClipboardHints(): void { diff --git a/src/app/components/query-explainer/query-explainer.component.html b/src/app/components/query-explainer/query-explainer.component.html index 16d119a0..5786c639 100644 --- a/src/app/components/query-explainer/query-explainer.component.html +++ b/src/app/components/query-explainer/query-explainer.component.html @@ -2,7 +2,7 @@ - +
@@ -31,32 +31,37 @@
Query:
-
+
-
+
Query dialect:
-
{{ inputParamsHelper(sqlQueryExplainer.input.queryDialect) }}
+
{{ inputParamsHelper(sqlQueryExplainerResponse.input.queryDialect) }}
Data format:
-
{{ inputParamsHelper(sqlQueryExplainer.input.dataFormat) }}
+
{{ inputParamsHelper(sqlQueryExplainerResponse.input.dataFormat) }}
Schema format:
-
{{ inputParamsHelper(sqlQueryExplainer.input.schemaFormat) }}
+
{{ inputParamsHelper(sqlQueryExplainerResponse.input.schemaFormat!) }}
Limit:
-
{{ sqlQueryExplainer.input.limit }}
+
{{ sqlQueryExplainerResponse.input.limit }}
Skip:
-
{{ sqlQueryExplainer.input.skip }}
+
{{ sqlQueryExplainerResponse.input.skip }}
Datasets:
- +
@@ -64,9 +69,7 @@
@@ -83,9 +86,10 @@
ODF Node DID:
- {{ sqlQueryExplainer.proof.verificationMethod }} + {{ sqlQueryExplainerResponse.proof.verificationMethod }}
Signature:
- - {{ sqlQueryExplainer.proof.proofValue }} + + {{ sqlQueryExplainerResponse.proof.proofValue }}
@@ -225,19 +222,15 @@
Reproduced result:
-
+
- - diff --git a/src/app/components/query-explainer/query-explainer.component.ts b/src/app/components/query-explainer/query-explainer.component.ts index a5598591..30757c9d 100644 --- a/src/app/components/query-explainer/query-explainer.component.ts +++ b/src/app/components/query-explainer/query-explainer.component.ts @@ -6,12 +6,22 @@ import { BaseComponent } from "src/app/common/base.component"; import { MaybeNull, MaybeUndefined } from "src/app/common/app.types"; import ProjectLinks from "src/app/project-links"; import { catchError, forkJoin, Observable, of, switchMap, tap } from "rxjs"; -import { QueryExplainerResponse, VerifyQueryResponse } from "./query-explainer.types"; +import { + QueryExplainerOutputType, + QueryExplainerResponse, + QueryExplainerSchemaType, + VerifyQueryDatasetBlockNotFoundError, + VerifyQueryDatasetNotFoundError, + VerifyQueryError, + VerifyQueryKindError, + VerifyQueryResponse, +} from "./query-explainer.types"; import { HttpErrorResponse } from "@angular/common/http"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { DataRow, DataSchemaField } from "src/app/interface/dataset.interface"; import { extractSchemaFieldsFromData } from "src/app/common/table.helper"; import { BlockService } from "src/app/dataset-block/metadata-block/block.service"; +import { changeCopyIcon } from "src/app/common/app.helpers"; @Component({ selector: "app-query-explainer", @@ -27,24 +37,25 @@ export class QueryExplainerComponent extends BaseComponent implements OnInit { private sqlQuery: MaybeNull; public readonly DATE_FORMAT = AppValues.DISPLAY_FLOW_DATE_FORMAT; + public readonly VerifyQueryKindError: typeof VerifyQueryKindError = VerifyQueryKindError; - public sqlQueryExplainer: QueryExplainerResponse; + public sqlQueryExplainerResponse: QueryExplainerResponse; public sqlQueryVerify$: Observable; public verifyResponse: VerifyQueryResponse; - public blockHashSystemTimes: string[] = []; + public blockHashSystemTimes: Date[] = []; ngOnInit(): void { this.sqlQuery = this.extractSqlQueryFromRoute(); if (this.sqlQuery) { - const blockHashObservables$: Observable[] = []; + const blockHashObservables$: Observable[] = []; this.queryExplainerService .proccessQuery(this.sqlQuery) .pipe( tap((response) => { - this.sqlQueryExplainer = response; + this.sqlQueryExplainerResponse = response; response.input.datasets - .map((dataset) => ({ datasetId: dataset.id, blockHash: dataset.blockHash })) + ?.map((dataset) => ({ datasetId: dataset.id, blockHash: dataset.blockHash })) .forEach(({ datasetId, blockHash }) => { blockHashObservables$.push( this.blockService.requestSystemTimeBlockByHash(datasetId, blockHash), @@ -72,22 +83,10 @@ export class QueryExplainerComponent extends BaseComponent implements OnInit { public copyToClipboard(event: MouseEvent, text: string): void { this.clipboard.copy(text); - if (event.currentTarget !== null) { - const currentElement: HTMLButtonElement = event.currentTarget as HTMLButtonElement; - const currentElementChildren: HTMLCollectionOf = - currentElement.children as HTMLCollectionOf; - setTimeout(() => { - currentElementChildren[0].style.display = "inline-block"; - currentElementChildren[1].style.display = "none"; - currentElement.classList.remove("clipboard-btn--success"); - }, AppValues.LONG_DELAY_MS); - currentElementChildren[0].style.display = "none"; - currentElementChildren[1].style.display = "inline-block"; - currentElement.classList.add("clipboard-btn--success"); - } + changeCopyIcon(event); } - private extractSqlQueryFromRoute(): MaybeNull { + private extractSqlQueryFromRoute(): string { const sqlQueryFromRoute: MaybeUndefined = this.activatedRoute.snapshot.queryParams[ ProjectLinks.URL_QUERY_PARAM_SQL_QUERY ] as MaybeUndefined; @@ -98,31 +97,61 @@ export class QueryExplainerComponent extends BaseComponent implements OnInit { return alias.includes("/") ? alias : `kamu/${alias}`; } - public get schemaFields(): DataSchemaField[] { - return extractSchemaFieldsFromData(this.tableSource[0]); + public schemaFields(output: QueryExplainerOutputType): DataSchemaField[] { + return extractSchemaFieldsFromData(this.tableSource(output)[0]); } public inputParamsHelper(option: string): string { switch (option) { case "SqlDataFusion": - return "Sql Data Fusion"; + return "SQL DataFusion"; case "JsonAoA": - return "Json AoA"; + return "JSON AoA"; case "ArrowJson": - return "Arrow Json"; + return "Arrow JSON"; default: return "Unknown options"; } } - private columnNames(): string[] { - return this.sqlQueryExplainer?.output?.schema.fields.map((item) => item.name) as string[]; + public isDatasetBlockNotFoundError(error: MaybeUndefined, blockHash: string): boolean { + return ( + error?.kind === VerifyQueryKindError.DatasetBlockNotFound && + (error as VerifyQueryDatasetBlockNotFoundError).block_hash === blockHash + ); + } + + public isDatasetNotFoundError(error: MaybeUndefined, datasetId: string): boolean { + return ( + error?.kind === VerifyQueryKindError.DatasetNotFound && + (error as VerifyQueryDatasetNotFoundError).dataset_id === datasetId + ); + } + + public isInputHashError(error: MaybeUndefined): boolean { + return error?.kind === VerifyQueryKindError.InputHash; + } + + public isSubQueriesHashError(error: MaybeUndefined): boolean { + return error?.kind === VerifyQueryKindError.SubQueriesHash; + } + + public isBadSignatureError(error: MaybeUndefined): boolean { + return error?.kind === VerifyQueryKindError.BadSignature; + } + + public isOutputMismatchError(error: MaybeUndefined): boolean { + return error?.kind === VerifyQueryKindError.OutputMismatch; + } + + private columnNames(schema: QueryExplainerSchemaType): string[] { + return schema.fields.map((item) => item.name); } - public get tableSource(): DataRow[] { - const result = this.sqlQueryExplainer?.output?.data.map((item) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return Object.assign({}, ...item.map((key, index) => ({ [this.columnNames()[index]]: key }))); + public tableSource(output: QueryExplainerOutputType): DataRow[] { + const result = output.data.map((dataItem) => { + const arr = dataItem.map((value, index) => ({ [this.columnNames(output.schema)[index]]: value })); + return arr.reduce((resultObj, obj) => Object.assign(resultObj, obj), {}); }) as DataRow[]; return result; } diff --git a/src/app/components/query-explainer/query-explainer.service.ts b/src/app/components/query-explainer/query-explainer.service.ts index a40e86b7..175dc15e 100644 --- a/src/app/components/query-explainer/query-explainer.service.ts +++ b/src/app/components/query-explainer/query-explainer.service.ts @@ -2,7 +2,7 @@ import { HttpClient, HttpErrorResponse } from "@angular/common/http"; import { inject, Injectable } from "@angular/core"; import { catchError, EMPTY, Observable } from "rxjs"; import { AppConfigService } from "src/app/app-config.service"; -import { QueryExplainerResponse, VerifyQueryResponse } from "./query-explainer.types"; +import { QueryExplainerInputType, QueryExplainerResponse, VerifyQueryResponse } from "./query-explainer.types"; import { ToastrService } from "ngx-toastr"; @Injectable({ @@ -15,9 +15,9 @@ export class QueryExplainerService { public proccessQuery(query: string): Observable { const url = new URL(`${this.appConfigService.apiServerHttpUrl}/query`); - const body = { + const body: QueryExplainerInputType = { query: query, - dataFormat: "JsonAoA", + dataFormat: "JsonAoa", schemaFormat: "ArrowJson", include: ["Proof"], }; diff --git a/src/app/components/query-explainer/query-explainer.types.ts b/src/app/components/query-explainer/query-explainer.types.ts index e23dc3ee..b07faf0c 100644 --- a/src/app/components/query-explainer/query-explainer.types.ts +++ b/src/app/components/query-explainer/query-explainer.types.ts @@ -1,41 +1,76 @@ +import { DataBatchFormat, DataSchemaFormat, QueryDialect } from "src/app/api/kamu.graphql.interface"; + export interface QueryExplainerResponse { - input: { - query: string; - queryDialect: string; - dataFormat: string; - schemaFormat: string; - include: string[]; - skip: number; - limit: number; - datasets: [{ alias: string; blockHash: string; id: string }]; - }; - commitment: { - inputHash: string; - outputHash: string; - subQueriesHash: string; - }; - proof: { - proofValue: string; - type: string; - verificationMethod: string; - }; - output?: { - data: [string[]]; - dataFormat: string; - schema: { - fields: [{ name: string }]; - }; - }; + input: QueryExplainerInputType; + commitment: QueryExplainerCommitmentType; + proof: QueryExplainerProofType; + output?: QueryExplainerOutputType; +} + +export interface QueryExplainerOutputType { + data: [string[]]; + dataFormat: keyof typeof DataBatchFormat; + schema: QueryExplainerSchemaType; +} + +export interface QueryExplainerSchemaType { + fields: [{ name: string }]; +} + +export interface QueryExplainerProofType { + proofValue: string; + type: string; + verificationMethod: string; +} + +export interface QueryExplainerCommitmentType { + inputHash: string; + outputHash: string; + subQueriesHash: string; +} + +export interface QueryExplainerInputType { + query: string; + include: string[]; + dataFormat: keyof typeof DataBatchFormat; + queryDialect?: keyof typeof QueryDialect; + schemaFormat?: keyof typeof DataSchemaFormat; + datasets?: [{ alias: string; blockHash: string; id: string }]; + skip?: number; + limit?: number; } export interface VerifyQueryResponse { ok: boolean; - error?: { - kind: string; - message: string; - actual_hash?: string; - expected_hash?: string; - dataset_id?: string; - block_hash?: string; - }; + error?: VerifyQueryError; +} + +export enum VerifyQueryKindError { + InputHash = "InvalidRequest::InputHash", + SubQueriesHash = "InvalidRequest::SubQueriesHash", + BadSignature = "InvalidRequest::BadSignature", + OutputMismatch = "VerificationFailed::OutputMismatch", + DatasetNotFound = "VerificationFailed::DatasetNotFound", + DatasetBlockNotFound = "VerificationFailed::DatasetBlockNotFound", +} + +export interface VerifyQueryError { + kind: VerifyQueryKindError; + message: string; +} + +export interface VerifyQueryInputHashError extends VerifyQueryError {} +export interface VerifyQuerySubQueriesHashError extends VerifyQueryError {} +export interface VerifyQueryBadSignatureError extends VerifyQueryError {} +export interface VerifyQueryOutputMismatchError extends VerifyQueryError { + expected_hash: string; + actual_hash: string; +} +export interface VerifyQueryDatasetNotFoundError extends VerifyQueryError { + dataset_id: string; +} + +export interface VerifyQueryDatasetBlockNotFoundError extends VerifyQueryError { + dataset_id: string; + block_hash: string; } diff --git a/src/app/dataset-block/metadata-block/block.service.ts b/src/app/dataset-block/metadata-block/block.service.ts index e78f7485..2e86b766 100644 --- a/src/app/dataset-block/metadata-block/block.service.ts +++ b/src/app/dataset-block/metadata-block/block.service.ts @@ -51,10 +51,10 @@ export class BlockService { ); } - public requestSystemTimeBlockByHash(datasetId: string, blockHash: string): Observable { + public requestSystemTimeBlockByHash(datasetId: string, blockHash: string): Observable { return this.datasetApi.getSystemTimeBlockByHash(datasetId, blockHash).pipe( map((data) => { - return data.datasets.byId?.metadata.chain.blockByHash?.systemTime ?? ""; + return new Date(data.datasets.byId?.metadata.chain.blockByHash?.systemTime ?? ""); }), ); }