Skip to content

Commit 47dc05c

Browse files
dmitriy-borzenkoDmitriy Borzenko
and
Dmitriy Borzenko
authored
Kamu UI 453 query view (#471)
* Integrated monaco editor with load mpre button * added search * Added template for empty schema * updated schema and graphql fragment * Moved query and result sections in the separated component * added separate component * added feature with adding schemes after query * Moved saved queries in the separate component * fixed tests regression * Modified css styles * added custom placeholder for monaco * Moved dataset schemas to the separate component * created SqlQueryService * Moved subscriptions into SqlQueryService * added ngonchanges lifecycle * Fixed unit tests regression * minor changes * Added unit tests * created shared-query module * changed CHANGELOG.md * Moved common components to shared folder * removed commented code --------- Co-authored-by: Dmitriy Borzenko <[email protected]>
1 parent eb7833d commit 47dc05c

File tree

62 files changed

+1818
-716
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1818
-716
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
### Added
10+
- Added a new page `Query` that allows you to make sql queries without being tied to a dataset
11+
12+
813
## [0.29.0] - 2024-11-12
914
### Fixed
1015
- Readme section refresh when navigating between datasets

resources/schema.graphql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ enum DataQueryResultErrorKind {
384384
type DataQueryResultSuccess {
385385
schema: DataSchema
386386
data: DataBatch!
387+
datasets: [DatasetState!]
387388
limit: Int!
388389
}
389390

@@ -706,6 +707,22 @@ type DatasetPermissions {
706707

707708
scalar DatasetRef
708709

710+
type DatasetState {
711+
"""
712+
Globally unique identity of the dataset
713+
"""
714+
id: DatasetID!
715+
"""
716+
Alias to be used in the query
717+
"""
718+
alias: String!
719+
"""
720+
Last block hash of the input datasets that was or should be considered
721+
during the query planning
722+
"""
723+
blockHash: Multihash
724+
}
725+
709726
enum DatasetVisibility {
710727
PRIVATE
711728
PUBLIC

src/app/api/gql/dataset-data-sql-run.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ query getDatasetDataSQLRun($query: String!, $limit: Int!, $skip: Int) {
44
query: $query
55
queryDialect: SQL_DATA_FUSION
66
schemaFormat: PARQUET_JSON
7-
dataFormat: JSON
7+
dataFormat: JSON_AOS
88
limit: $limit
99
skip: $skip
1010
) {

src/app/api/gql/fragments/fragment-data-query-result-success.graphql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,9 @@ fragment DataQueryResultSuccessView on DataQueryResultSuccess {
77
format
88
content
99
}
10+
datasets {
11+
id
12+
alias
13+
blockHash
14+
}
1015
}

src/app/api/kamu.graphql.interface.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ export enum DataQueryResultErrorKind {
446446
export type DataQueryResultSuccess = {
447447
__typename?: "DataQueryResultSuccess";
448448
data: DataBatch;
449+
datasets?: Maybe<Array<DatasetState>>;
449450
limit: Scalars["Int"];
450451
schema?: Maybe<DataSchema>;
451452
};
@@ -796,6 +797,19 @@ export type DatasetPermissions = {
796797
canView: Scalars["Boolean"];
797798
};
798799

800+
export type DatasetState = {
801+
__typename?: "DatasetState";
802+
/** Alias to be used in the query */
803+
alias: Scalars["String"];
804+
/**
805+
* Last block hash of the input datasets that was or should be considered
806+
* during the query planning
807+
*/
808+
blockHash?: Maybe<Scalars["Multihash"]>;
809+
/** Globally unique identity of the dataset */
810+
id: Scalars["DatasetID"];
811+
};
812+
799813
export enum DatasetVisibility {
800814
Private = "PRIVATE",
801815
Public = "PUBLIC",
@@ -3663,6 +3677,7 @@ export type DataQueryResultSuccessViewFragment = {
36633677
__typename?: "DataQueryResultSuccess";
36643678
schema?: { __typename?: "DataSchema"; format: DataSchemaFormat; content: string } | null;
36653679
data: { __typename?: "DataBatch"; format: DataBatchFormat; content: string };
3680+
datasets?: Array<{ __typename?: "DatasetState"; id: string; alias: string; blockHash?: string | null }> | null;
36663681
};
36673682

36683683
export type DatasetBasicsFragment = {
@@ -4648,6 +4663,11 @@ export const DataQueryResultSuccessViewFragmentDoc = gql`
46484663
format
46494664
content
46504665
}
4666+
datasets {
4667+
id
4668+
alias
4669+
blockHash
4670+
}
46514671
}
46524672
`;
46534673
export const DatasetDataFragmentDoc = gql`
@@ -5929,7 +5949,7 @@ export const GetDatasetDataSqlRunDocument = gql`
59295949
query: $query
59305950
queryDialect: SQL_DATA_FUSION
59315951
schemaFormat: PARQUET_JSON
5932-
dataFormat: JSON
5952+
dataFormat: JSON_AOS
59335953
limit: $limit
59345954
skip: $skip
59355955
) {

src/app/app-routing.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { GlobalQueryComponent } from "./query/global-query/global-query.component";
12
import { AddPollingSourceComponent } from "./dataset-view/additional-components/metadata-component/components/source-events/add-polling-source/add-polling-source.component";
23
import { MetadataBlockComponent } from "./dataset-block/metadata-block/metadata-block.component";
34
import { AuthenticatedGuard } from "./auth/guards/authenticated.guard";
@@ -52,6 +53,11 @@ export const routes: Routes = [
5253
component: QueryExplainerComponent,
5354
loadChildren: () => import("./query-explainer/query-explainer.module").then((m) => m.QueryExplainerModule),
5455
},
56+
{
57+
path: ProjectLinks.URL_QUERY,
58+
component: GlobalQueryComponent,
59+
loadChildren: () => import("./query/query.module").then((m) => m.QueryModule),
60+
},
5561
{
5662
path:
5763
`:${ProjectLinks.URL_PARAM_ACCOUNT_NAME}/:${ProjectLinks.URL_PARAM_DATASET_NAME}` +

src/app/app.component.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { LoginService } from "./auth/login/login.service";
2626
import { HttpClientTestingModule } from "@angular/common/http/testing";
2727
import { NotificationIndicatorComponent } from "./components/notification-indicator/notification-indicator.component";
2828
import { AngularSvgIconModule, SvgIconRegistryService } from "angular-svg-icon";
29+
import { MatIconModule } from "@angular/material/icon";
2930

3031
describe("AppComponent", () => {
3132
let component: AppComponent;
@@ -46,6 +47,7 @@ describe("AppComponent", () => {
4647
FormsModule,
4748
HttpClientTestingModule,
4849
RouterModule,
50+
MatIconModule,
4951
AngularSvgIconModule.forRoot(),
5052
],
5153
declarations: [

src/app/common/app.values.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default class AppValues {
3232
public static readonly HEADERS_AUTHORIZATION_KEY = "Authorization";
3333
public static readonly UPLOAD_FILE_IMAGE = "assets/images/upload-file-gear.gif";
3434
public static readonly DEFAULT_ADMIN_ACCOUNT_NAME = "kamu";
35+
public static readonly DEFAULT_MONACO_EDITOR_PLACEHOLDER = "Please type your query here...";
3536

3637
public static readonly MARKDOWN_CONTAIN = `## Markdown __rulez__!
3738
---

src/app/common/data.helpers.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import { MaybeNull } from "src/app/common/app.types";
22
import { AbstractControl, ValidationErrors, ValidatorFn, Validators } from "@angular/forms";
3-
import { FlowSummaryDataFragment, MetadataBlockFragment, TimeUnit } from "../api/kamu.graphql.interface";
3+
import {
4+
DataQueryResultSuccessViewFragment,
5+
FlowSummaryDataFragment,
6+
MetadataBlockFragment,
7+
TimeUnit,
8+
} from "../api/kamu.graphql.interface";
49
import { EventPropertyLogo } from "../dataset-block/metadata-block/components/event-details/supported.events";
510
import { JsonFormValidators } from "../dataset-view/additional-components/metadata-component/components/source-events/add-polling-source/add-polling-source-form.types";
611
import { MaybeUndefined } from "./app.types";
712
import { RxwebValidators } from "@rxweb/reactive-form-validators";
813
import { isValidCronExpression } from "./cron-expression-validator.helper";
914
import { ErrorPolicy, WatchQueryFetchPolicy } from "@apollo/client";
1015
import moment from "moment";
11-
import { convertSecondsToHumanReadableFormat } from "./app.helpers";
16+
import { convertSecondsToHumanReadableFormat, removeAllLineBreaks } from "./app.helpers";
1217
import { SliceUnit } from "../dataset-view/additional-components/dataset-settings-component/tabs/compacting/dataset-settings-compacting-tab.types";
18+
import { DataRow, DatasetSchema } from "../interface/dataset.interface";
1319

1420
export class DataHelpers {
1521
public static readonly BLOCK_DESCRIBE_SEED = "Dataset initialized";
@@ -357,3 +363,12 @@ export function sliceSizeMapperReverse(sizeInBytes: number): { size: number; uni
357363
return { size: sizeInBytes / Math.pow(2, 10), unit: SliceUnit.KB };
358364
}
359365
}
366+
367+
export function parseSchema(schemaContent: string): DatasetSchema {
368+
return JSON.parse(removeAllLineBreaks(schemaContent)) as DatasetSchema;
369+
}
370+
371+
export function parseDataRows(successResult: DataQueryResultSuccessViewFragment): DataRow[] {
372+
const content: string = successResult.data.content;
373+
return JSON.parse(content) as DataRow[];
374+
}

src/app/components/app-header/app-header.component.html

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,24 +107,34 @@
107107
>
108108
</ng-template>
109109

110+
<a
111+
class="header-link d-flex align-items-center py-2 py-md-3 mr-4 mr-md-3 border-sm-top border-md-top-0 border-white-fade"
112+
[routerLink]="[URL_QUERY]"
113+
>
114+
<mat-icon class="fs-4">query_stats</mat-icon>
115+
<span> Query</span>
116+
</a>
117+
110118
<ng-template [ngIf]="isUserLoggedIn()">
111119
<a
112-
class="header-link py-2 py-md-3 mr-0 mr-md-3 border-sm-top border-md-top-0 border-white-fade"
120+
class="header-link d-flex align-items-center py-2 py-md-3 mr-4 mr-md-3 border-sm-top border-md-top-0 border-white-fade"
113121
data-ga-click="Header, click, Nav menu - item:dashboard:user"
114122
data-test-id="userDatasetsHeader"
115123
aria-label="Dashboard"
116124
[routerLink]="['/', loggedAccount.accountName]"
117125
[queryParams]="{ tab: AccountTabs.DATASETS }"
118126
>
119-
Your Datasets
127+
<mat-icon class="fs-4">book</mat-icon>
128+
<span>Your Datasets </span>
120129
</a>
121130

122131
<a
123-
class="header-link py-2 py-md-3 mr-0 mr-md-3 border-sm-top border-md-top-0 border-white-fade"
132+
class="header-link d-flex align-items-center py-2 py-md-3 mr-4 mr-md-3 border-sm-top border-md-top-0 border-white-fade"
124133
(click)="onDashboard()"
125134
*ngIf="isAdmin"
126135
>
127-
Dashboard
136+
<mat-icon class="fs-4">dashboard</mat-icon>
137+
<span> Dashboard</span>
128138
</a>
129139

130140
<a
@@ -203,7 +213,7 @@
203213
</div>
204214
<ng-container *ngIf="isUserLoggedIn()">
205215
<a
206-
class="app-header__right-column__addnew-block d-block d-sm-none d-md-flex m-2"
216+
class="app-header__right-column__addnew-block header-link d-block d-sm-none d-md-flex m-2"
207217
[routerLink]="[URL_DATASET_CREATE]"
208218
data-test-id="addNewBlock"
209219
>

src/app/components/app-header/app-header.component.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { AngularSvgIconModule } from "angular-svg-icon";
3131
import { HttpClientTestingModule } from "@angular/common/http/testing";
3232
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
3333
import { LoginMethod } from "src/app/app-config.model";
34+
import { MatIconModule } from "@angular/material/icon";
3435

3536
describe("AppHeaderComponent", () => {
3637
let component: AppHeaderComponent;
@@ -55,6 +56,7 @@ describe("AppHeaderComponent", () => {
5556
AngularSvgIconModule.forRoot(),
5657
HttpClientTestingModule,
5758
RouterModule,
59+
MatIconModule,
5860
],
5961
declarations: [AppHeaderComponent, NotificationIndicatorComponent],
6062
providers: [

src/app/components/app-header/app-header.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export class AppHeaderComponent extends BaseComponent implements OnInit {
6767
public readonly HOME_LINK = ProjectLinks.URL_SEARCH;
6868
public readonly URL_DATASET_CREATE = ProjectLinks.URL_DATASET_CREATE;
6969
public readonly URL_SETTINGS = ProjectLinks.URL_SETTINGS;
70+
public readonly URL_QUERY = ProjectLinks.URL_QUERY;
7071

7172
private appSearchAPI = inject(SearchApi);
7273
private route = inject(ActivatedRoute);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
fieldset {
2+
&.fieldset-border {
3+
border: 1px solid #dee2e6;
4+
padding: 0 1.4em 1em;
5+
margin: 0 0 0.5em;
6+
border-radius: 6px;
7+
}
8+
9+
legend {
10+
&.fieldset-border {
11+
font-size: 12px;
12+
font-weight: bold;
13+
width: auto;
14+
padding: 0 10px;
15+
position: relative;
16+
top: -10px;
17+
background: white;
18+
}
19+
}
20+
}

src/app/components/data-access-panel/data-access-modal/tabs/data-access-stream-tab/data-access-stream-tab.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { KafkaProtocolDesc, WebSocketProtocolDesc } from "src/app/api/kamu.graph
55
@Component({
66
selector: "app-data-access-stream-tab",
77
templateUrl: "./data-access-stream-tab.component.html",
8+
styleUrls: ["./data-access-stream-tab.component.scss"],
89
changeDetection: ChangeDetectionStrategy.OnPush,
910
})
1011
export class DataAccessStreamTabComponent extends DataAccessBaseTabComponent {

src/app/components/data-access-panel/data-access-panel.component.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ import { ApolloTestingModule } from "apollo-angular/testing";
1616
import { ProtocolsService } from "src/app/services/protocols.service";
1717
import { of } from "rxjs";
1818
import { mockDatasetEndPoints } from "./data-access-panel-mock.data";
19+
import { NgbModal } from "@ng-bootstrap/ng-bootstrap";
1920

2021
describe("DataAccessPanelComponent", () => {
2122
let component: DataAccessPanelComponent;
2223
let fixture: ComponentFixture<DataAccessPanelComponent>;
2324
let protocolsService: ProtocolsService;
25+
let ngbModalService: NgbModal;
2426

2527
beforeEach(async () => {
2628
await TestBed.configureTestingModule({
@@ -44,6 +46,7 @@ describe("DataAccessPanelComponent", () => {
4446

4547
fixture = TestBed.createComponent(DataAccessPanelComponent);
4648
protocolsService = TestBed.inject(ProtocolsService);
49+
ngbModalService = TestBed.inject(NgbModal);
4750
component = fixture.componentInstance;
4851
component.datasetBasics = mockDatasetBasicsDerivedFragment;
4952
spyOn(protocolsService, "getProtocols").and.returnValue(of(mockDatasetEndPoints));
@@ -53,4 +56,10 @@ describe("DataAccessPanelComponent", () => {
5356
it("should create", () => {
5457
expect(component).toBeTruthy();
5558
});
59+
60+
it("should check open modal window", () => {
61+
const ngbModalOpenSpy = spyOn(ngbModalService, "open").and.callThrough();
62+
component.openDataAccessModal();
63+
expect(ngbModalOpenSpy).toHaveBeenCalledTimes(1);
64+
});
5665
});

0 commit comments

Comments
 (0)