Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kamu UI 423 query explainer UI #452

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

dmitriy-borzenko
Copy link
Contributor

@dmitriy-borzenko dmitriy-borzenko commented Oct 17, 2024

Closes: #423

Result:
image

image

@dmitriy-borzenko dmitriy-borzenko linked an issue Oct 17, 2024 that may be closed by this pull request
@@ -46,6 +47,11 @@ export const routes: Routes = [
path: ProjectLinks.URL_ADMIN_DASHBOARD,
component: AdminDashboardComponent,
},
{
canActivate: [AdminGuard],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ticket is not saying anything that this new page should be limited to admins

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will clarify the requirements

<div
class="alert alert-info message-success mx-4 mt-4 fs-18 w-50 mx-auto d-flex align-items-center justify-content-center"
role="alert"
*ngIf="verifyResponse?.ok"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've checked verifyResponse in the upper tag. If you are here, it cannot be undefined or null.
Correct everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

private sqlQuery: MaybeNull<string>;
public readonly DATE_FORMAT = AppValues.DISPLAY_FLOW_DATE_FORMAT;

public sqlQueryExplainer: QueryExplainerResponse;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use word "response" in the variable name too. Right now it looks like it's a component

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment on lines 31 to 33
public sqlQueryExplainer: QueryExplainerResponse;
public sqlQueryVerify$: Observable<VerifyQueryResponse>;
public verifyResponse: VerifyQueryResponse;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So input sqlQuery may be null, but the resulting data can't? I'm not sure this typing is accurate.

.proccessQuery(this.sqlQuery)
.pipe(
tap((response) => {
this.sqlQueryExplainer = response;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we move the response into a separate observable, perhaps using sharedReplay operator to reuse the value?

return of(e.error as VerifyQueryResponse);
}),
tap((data) => {
this.verifyResponse = data;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this should be stored as an observable.

tap((data) => {
this.verifyResponse = data;
}),
switchMap(() => forkJoin(blockHashObservables$)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blockHashObservables$ as a local variable... this is whatever, but not functional reactive programming.

Comment on lines 76 to 86
const currentElement: HTMLButtonElement = event.currentTarget as HTMLButtonElement;
const currentElementChildren: HTMLCollectionOf<HTMLElement> =
currentElement.children as HTMLCollectionOf<HTMLElement>;
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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very low level and unclear.
Maybe naming the element's role and children roles may help to understand this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment on lines 90 to 95
private extractSqlQueryFromRoute(): MaybeNull<string> {
const sqlQueryFromRoute: MaybeUndefined<string> = this.activatedRoute.snapshot.queryParams[
ProjectLinks.URL_QUERY_PARAM_SQL_QUERY
] as MaybeUndefined<string>;
return sqlQueryFromRoute ?? "";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How it can be null, if you have a default value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

}

public routeToDataset(alias: string): string {
return alias.includes("/") ? alias : `kamu/${alias}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't understand this. We have other users than kamu, what is the hack doing?

Copy link
Contributor Author

@dmitriy-borzenko dmitriy-borzenko Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use single-tenant image, we don't receive an account in the alias in the response.
Examlple:
account.tokens.portfolio - single-tenant image
kamu/account.tokens.portfolio - multi-tenant image

Comment on lines 105 to 116
public inputParamsHelper(option: string): string {
switch (option) {
case "SqlDataFusion":
return "Sql Data Fusion";
case "JsonAoA":
return "Json AoA";
case "ArrowJson":
return "Arrow Json";
default:
return "Unknown options";
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of doubt about spelling.
Sql => SQL
Data Fusion => DataFusion
Json => JSON

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment on lines 118 to 120
private columnNames(): string[] {
return this.sqlQueryExplainer?.output?.schema.fields.map((item) => item.name) as string[];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it accept a parameter that is already checked against null/undefined.
I.e. this.sqlQueryExplainer.output. could come from HTML template within checked divs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment on lines 122 to 128
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 })));
}) as DataRow[];
return result;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I see a type cast pragma, it means the code is wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, removed pragma

Comment on lines 20 to 21
dataFormat: "JsonAoA",
schemaFormat: "ArrowJson",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have these constants in GraphQL generated client?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes there are, but only as enum keys

Comment on lines 4 to 6
queryDialect: string;
dataFormat: string;
schemaFormat: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These look like they are enums by nature

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Comment on lines 19 to 20
type: string;
verificationMethod: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These look like enums too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, in this place string

image

};
output?: {
data: [string[]];
dataFormat: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this definitely is an enum

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed, set dataFormat: keyof typeof DataBatchFormat;

Comment on lines 33 to 40
error?: {
kind: string;
message: string;
actual_hash?: string;
expected_hash?: string;
dataset_id?: string;
block_hash?: string;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand, this is modeling a few types of errors which may nor may not have some of these parameters.
I prefer if we define a separate clean structure per each type of error that is hidden here, and union them in the field. The code that is using this will first test for kind, which is shared, and downcast to particular good error structures depending on the kind.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added separate error interface for all kind of errors

Comment on lines 53 to 60

public requestSystemTimeBlockByHash(datasetId: string, blockHash: string): Observable<string> {
return this.datasetApi.getSystemTimeBlockByHash(datasetId, blockHash).pipe(
map((data) => {
return data.datasets.byId?.metadata.chain.blockByHash?.systemTime ?? "";
}),
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why time is returned as string? It should return time

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, changed to Date type

</div>
</div>
</button>
</div>
<ng-container *ngIf="isAdmin">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, where is the requirement written about opening this only for admins

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, only for admins

Copy link
Contributor

@zaychenko-sergei zaychenko-sergei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I recommend to split the main observable into several reusable observables and avoiding state variables in the component. Use operators like sharedReplay when reusing lower-level observables to avoid re-querying the source for multiple derived observables.

  2. The Query Explainer component looks very big to me. Consider splitting it into more manageable smaller parts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Query explainer UI
2 participants