An open API service indexing awesome lists of open source software.

https://github.com/hypertrace/hypertrace-ui

UI for Hypertrace
https://github.com/hypertrace/hypertrace-ui

Last synced: 7 months ago
JSON representation

UI for Hypertrace

Awesome Lists containing this project

README

          



Logo

Hypertrace-UI



User interface for an open source distributed tracing & observability platform!


More about hypertrace »


## Table of Contents

- [Setup](#setup)
- [Technologies](#technologies)
- [Code Architecture](#code-architecture)
- [The Essentials](#the-essentials)
- [Angular Specifics](#angular-specifics)
- [Tables](#tables)
- [Cell Renderers](#cell-renderers)
- [Dashboards](#dasboards)
- [Widget](#widget)
- [Widget Renderer](#widget-renderer)
- [Data Source](#data-source)
- [Graphql Handlers](#graphql-handlers)
- [Testing](#testing)
- [Best Practices](#best-practices)
- [Contributions](#contributions)
- [Others](#others)
- [Building Image locally](#building-image-locally)
- [Docker Image Source](#docker-image-source)

## Setup

Install `Node` and `npm`, if not done already ([Node and Npm](https://www.npmjs.com/get-npm)). Recommended node version is `16+`.

[Fork](<(https://docs.github.com/en/github/getting-started-with-github/fork-a-repo)>) or clone the repository and Use following commands.

```sh
cd
npm ci
```

once done, start a development server

```sh
npm start
```

Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.

To run unit test cases (execute the unit tests via Jest)

```sh
npm run test
```

## Technologies

Hypertrace-ui uses `angular` as the framework. On top of angular, following technologies are being used.

1. `Typescript` : As a core language, hypertrace-ui uses typescript instead of traditional javascript. Learn more about Typescript [here](https://www.typescriptlang.org/docs/)
2. `RxJS` : For reactive programming, hypertrace-ui uses RxJs library. Learn more about RxJs [here](https://rxjs.dev/api)
3. `D3.js` : Charts are the core part for hypertrace-ui. Charts are custom build here and use D3.js library. Learn more about D3.js [here](https://d3js.org/)
4. `Spectator`: For unit testing, hypertrace-ui uses spectator library. Learn more about Spectator [here](https://github.com/ngneat/spectator)
5. `Hyperdash & Hyperdash-Angular`: Hyperdash is dashboarding framework/library and hyperdash-angular is a wrapper, specific to angular. Learn more about dasboards in [dashboards](#dashboards) section.

## Code Architecture

Hypertrace-UI code is divided into many smaller projects which gives the code base a better structure. Here are the projects.

1. `Assest Library` : This consists of the assets which are being used in the application. For example; images, icons etc. Check this out [here](https://github.com/hypertrace/hypertrace-ui/tree/main/projects/assets-library)
2. `Common` : This consists of the common code/features such as application constants, colors, telemetry, utilities (with common angular pipes) etc. Check this out [here](https://github.com/hypertrace/hypertrace-ui/tree/main/projects/common)
3. `Components` : Hypertrace-ui has a wide variety of custom made angular components. This is the place for generic components (eg. `Input` Component) and directives(eg. `LoadAsync` Directive). Check this out [here](https://github.com/hypertrace/hypertrace-ui/tree/main/projects/components)
4. `Dashboards` : This consists of the common code for dashboards such as base model, properties etc. Check this out [here](https://github.com/hypertrace/hypertrace-ui/tree/main/projects/dashboards)
5. `Graphql-Client` : Hypertrace-ui uses [apollo graphql](https://www.apollographql.com/) for API calls. This is the place where all the base graphql request related code is present such as graphql arguments, resolvers, builders etc. Check this out [here](https://github.com/hypertrace/hypertrace-ui/tree/main/projects/graphql-client)
6. `Observability` : This consists of all the different pages, components, services related to distributed tracing and observability. This project is the home for charts as well. Check this out [here](https://github.com/hypertrace/hypertrace-ui/tree/main/projects/observability)
7. `Test Utils` : This consists of some unit test utilities for dashboards etc.. Check this out [here](https://github.com/hypertrace/hypertrace-ui/tree/main/projects/test-utils)
8. `UI App` : This is not a project but a entry point for hypertrace-ui app. This consists of the home page, routes, config module etc. Check this out [here](https://github.com/hypertrace/hypertrace-ui/tree/main/src)

`NOTE` : Each project contains a barrel file named `public-api.ts`. This handles all the exports at a single place which improves the importing in the app.
For example

```ts
@import { Color } from '@hypertrace/common'
```

## The Essentials

Let's talk about the essentials for development in hypertrace-ui.

### Angular Specifics

Since hypertrace-ui uses `angular` as core framework, all the concepts specific to angular are being used and applied in hypertrace-ui. Such as `components`, `directives`, `pipes`, `dependency injection`, `services`, `modules`, `lazy loading` etc. Check out the angular [docs](https://angular.io/docs) for more info.

`NOTE` : Test file name ends with `.test.ts` instead of `.spec.ts` for better readability.

### Dashboards

Hypertrace-UI uses dashboards to build custom pages. These dashboards are widely used in the application. These dashboards are build using `Hyperdash` and `Hyperdash-Angular` libraries. Check out both here ([Hyperdash](https://github.com/hypertrace/hyperdash/blob/main/README.md) & [Hyperdash angular](https://github.com/hypertrace/hyperdash-angular/blob/main/README.md))


Let's check this example.

`in Template`

```html

```

`in Component`

```ts
public readonly location: string = 'HELLO_LOCATION';
public readonly defaultJson: ModelJson = {
type: 'hello-widget',
name: 'name'
children: [],
data: {
'upper-case': false,
type: 'hello-data-source'
}
}
```

Now let's break this down.

- It will create a dasboard for a unique location.
- Dasboards are designed in a way that it takes a modal json as input property and renders the corresponding widgets.
- There are 3 core concepts - widget , widget renderer and data source.

Let's talk about these individually.

#### Widget

To create a widget, we need to create model class.


Continue with the above `ModelJson`. Let's create `hello-widget.model.ts`

```ts
import { Model, ModelProperty, STRING_PROPERTY } from '@hypertrace/hyperdash';

@Model({
type: 'hello-widget',
})
export class HelloWidgetModel {
@ModelProperty({
type: STRING_PROPERTY.type,
key: 'name',
required: false,
})
public name?: string;
}
```

Now let's break this down.

- We have used decorator `Model` by which we're registering this widget (on build) for usage.
- `type` property is the unique string to define each widget. If we look closely we have used the same string as a type in the model json as well.
- We have used decorator `ModelProperty` for defining the custom properties like in this case `name`.

But the question is how this class will render the dom? let's find out in next section!

#### Widget Renderer

Now after creating the widget model, let's create widget renderer component.


Using the same example. Let's create `hello-widget-renderer.component.ts

```ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Renderer } from '@hypertrace/hyperdash';
import { Observable } from 'rxjs';
import { WidgetRenderer } from '../widget-renderer';
import { HelloWidgetModel } from './hello-widget.model';

@Renderer({ modelClass: HelloWidgetModel })
@Component({
selector: 'ht-hello-widget-renderer',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `

{{ data }} {{ this.model.name }}
`,
})
export class HelloWidgetRendererComponent extends WidgetRenderer {
protected fetchData(): Observable {
return this.api.getData();
}
}
```

Now let's break this down.

- We have used decorator `Renderer` by which we're registering this widget renderer (on build) for usage.
- `modelClass` property is the same class for which we're building this renderer, in this case it is `HelloWidgetModel`.
- Now after extending the `WidgetRenderer` class, we have access to the `name` property which we defined in the model class.
- `fetchData` method is used to give us access to `this.data$` observable.
- `htLoadAsync` directive is used to resolve the data observable, which can be done with `async` pipe as well.

How are we getting data? Now let's understand this in the next section.

#### Data Source

Now after creating the widget renderer we need the data which we are using in the renderer component.


Continue with the above `ModelJson`. Let's create `hello-data-source.model.ts`

```ts
import { Model, ModelProperty, STRING_PROPERTY} from '@hypertrace/hyperdash';
import { Observable, of } from 'rxjs';

@Model({
type: 'hello-data-source'
})
export class HelloDataSourceModel {
@ModelProperty({
key: 'upper-case',
required: false,
type: STRING_PROPERTY.type
})
public upperCase: boolean = false;

public getData(): Observable {
return of(this.upperCase ? 'HELLO' ? 'Hello')
}
}
```

Now let's break this down.

- We have used decorator `Model` by which we're registering this data source (on build) for usage.
- `type` property is the unique string to define each data source. If we look closely we have used the same string as a type in the model json as well for key `data`.
- We have used decorator `ModelPropery` for defining custom properties; like in this case `upper-case`.
- We have implemented `getData` method, and the same method is being used in the renderer component `return this.api.getData()`.

Now after implemening all this we can use these as shown above and render custom data.

_Why we're doing this_? Answer is simple, once we do all this, we can just write few lines of json to render a whole widget. For more complex examples please check `home.dashboard.ts` and its constituent widgets.

### Tables

Table is a custom component in the hypertrace-ui. The following example shows how to add table inside another component.


Here is what table API Says:

```ts
@Input()
public data?: TableDataSource;
```

```ts
@Input()
public columnConfigs?: TableColumnConfig[];
```

So Let's use this.
`in Template`

```html

```

`in Component`

```ts
public datasource?: TableDataSource = {
getData : () => of({
data: [{name: 'test-name1'}, {name: 'test-name2'}],
totalCount: 2
})
};
public readonly columnConfigs: TableColumnConfig[] = [
{
id: 'name',
title: 'Name'
visible: true,
sortable: false,
width: '48px',
}
];
```

Now let's break this down.

- It will create a table with a single column (In column configs we have only mentioned one column) and two rows (in the data source we have mentioned data as array of two objects.).
- If we look closely, table column config's id is same as the key we have used the key in data which is `name`. This is a must for the table to render the data correctly.

There are more features in the table component, like custom controls, configurations (for pagination and many other). We highly recommend you to check out the `table.component.ts` to learn about tables and check out all the different examples present in the application.

#### Cell Renderers

Continuing from tables, let's talk about custom table cell renderers. In hypertrace-ui, we can create a custom table cell renderer to handle presentation of a cell data. These are nothing but angular component with another decorator `TableCellRenderer`


Now let's see how we can create a custom cell renderer. for example `hello-table-cell-renderer.component.ts`

```ts
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { TableCellRenderer } from '../../table-cell-renderer';
import { TableCellRendererBase } from '../../table-cell-renderer-base';
import { CoreTableCellParserType } from '../../types/core-table-cell-parser-type';
import { TableCellAlignmentType } from '../../types/table-cell-alignment-type';

@Component({
selector: 'ht-hello-table-cell-renderer',
styleUrls: ['./hello-table-cell-renderer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `

Hello {{ this.value }}
`,
})
@TableCellRenderer({
type: 'hello',
alignment: TableCellAlignmentType.Left,
parser: CoreTableCellParserType.NoOp,
})
export class HelloTableCellRendererComponent extends TableCellRendererBase {}
```

Now let's break this down.

- We have used decorator `TableCellRenderer` by which we're registering this cell renderer (on build) for usage.
- `type` property is the unique string to define each cell renderer.
- `alignment` is used for the cell alignment, could be - left, right and center
- `parser` is used to parse the data. It can also be defined similar to a cell renderer. Suppose we are getting data as `['test1', 'test2']` and we want it to be marshalled into `{value1: 'test1', value2: 'test2'}` then we can create a custom parser and can handle the transformation. For no operation we use `CoreTableCellParserType.NoOp`

`Usage` : Once this is done, we can use this in our table using `display` property. This will be same as the type of the cell renderer.

`Module import`

```ts
TableModule.withCellRenderers([HelloTableCellRendererComponent]);
```

`in Component`

```ts
public readonly columnConfigs: TableColumnConfig[] = [
{
id: 'name',
title: 'Name'
display: 'hello'
visible: true,
sortable: false,
width: '48px',
}
];
```

`NOTE`: We highly recommend you to check out all the existing example of table and cell renderers to learn more.

## GraphQl Handlers

There are two type of graphql handlers we use.

1. `Query` : To get the data from server / backend.
2. `Mutation` : For delete, post and put use cases.

Let's see an example of a query graphql-handler:

```ts
import { Injectable } from '@angular/core';
import { GraphQlHandlerType, GraphQlQueryHandler, GraphQlSelection } from '@hypertrace/graphql-client';

@Injectable({ providedIn: 'root' })
export class HelloGraphQlQueryHandlerService implements GraphQlQueryHandler {
public readonly type: GraphQlHandlerType.Query = GraphQlHandlerType.Query;

public matchesRequest(request: unknown): request is GraphQlHelloRequest {
return (
typeof request === 'object' &&
request !== null &&
(request as Partial).requestType === HELLO_GQL_REQUEST
);
}

public convertRequest(request: GraphQlHelloRequest): GraphQlSelection {
return {
path: 'getHello',
children: [
{
path: 'result',
},
],
};
}

public convertResponse(response: ExportSpansResponse): string | undefined {
return response.result;
}
}

export const HELLO_GQL_REQUEST = Symbol('GraphQL Hello Request');

export interface GraphQlHelloRequest {
requestType: typeof HELLO_GQL_REQUEST;
}

export interface GraphQlHelloResponse {
result: string;
}
```

Let's break this down.

- We have used a unique symbol, as a type for the request.
- Two interfaces `GraphQlHelloRequest` and `GraphQlHelloResponse` for request and response.
- `matchesRequest` method is used to verify the request.
- `convertRequest` method is used for converting request into graphql selection.
- `convertResponse` method is used to convert the response into desired format.

`Usage` : Now once this is done, we can use this in the service/component

`Module import`

```ts
GraphQlModule.withHandlerProviders([HelloGraphQlQueryHandlerService]);
```

`in Component/Service`

```ts
// Injection
private readonly graphQlQueryService: GraphQlRequestService

// Usage
this.graphQlQueryService.query({
requestType: HELLO_GQL_REQUEST
})
```

## Testing

Testing is an integral part of hypertrace-ui and hypertrace-ui maintains a good amount of code coverage using unit tests! We use `Spectator` library to test components. services, directives, pipes, dashboards etc. We always write `shallow` tests.

## Best Pratices

1. `Naming`: Always write a file and class name which is easy to understand and specific to use case. for example - asynchronous loading -> directive name is `LoadAsyncDirective`. Use `ht` as prefix in component selectors, pipes, directives etc. There is lint rule as well.
2. `Linting`: For a consistent in file code structure, linting is used and a requirement before merging the code.

## Contributions

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

## Others

These are some extra things that might be useful.

#### Building Image locally

Hypertrace UI uses gradle to build a docker image. Gradle wrapper is already part of the source code. To build Hypertrace UI image, run:

```sh
./gradlew dockerBuildImages
```

#### Docker Image Source:

- [DockerHub > Hypertrace UI](https://hub.docker.com/r/hypertrace/hypertrace-ui)