Skip to content

Commit 0f549e7

Browse files
authored
feat: Add first implementation of environment status (#284)
1 parent 17fe59e commit 0f549e7

30 files changed

+757
-45
lines changed

.codacy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
exclude_paths:
33
- "**.spec.ts"
44
- "**/test/**"
5-
- "**/test-setup.ts"
5+
- "**/test-setup.ts"

client/src/app/components/environments/environment-list/environment-list-view.component.html

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
<app-deployment-state-tag [state]="deployment.state || 'IN_PROGRESS'" />
2727
}
2828

29+
@if (environment.latestStatus; as status) {
30+
<app-environment-status-tag [status]="status" />
31+
}
32+
2933
<div class="flex-grow"></div>
3034

3135
@if (environment.locked) {
@@ -61,9 +65,20 @@
6165
</div>
6266
</p-accordion-header>
6367
<p-accordion-content>
64-
@if (environment.latestDeployment; as deployment) {
65-
<app-environment-deployment-info [deployment]="deployment" [repositoryId]="environment.repository?.id || 0" [installedApps]="environment.installedApps || []" />
66-
}
68+
<div class="flex justify-between max-w-6xl">
69+
@if (environment.latestDeployment; as deployment) {
70+
<app-environment-deployment-info
71+
class="flex-grow max-w-2xl"
72+
[deployment]="deployment"
73+
[repositoryId]="environment.repository?.id || 0"
74+
[installedApps]="environment.installedApps || []"
75+
/>
76+
}
77+
78+
@if (environment.latestStatus; as status) {
79+
<app-environment-status-info class="flex-grow max-w-xs mt-2 ml-14" [status]="status" />
80+
}
81+
</div>
6782

6883
<div class="flex gap-4 items-center justify-between">
6984
<div class="flex gap-1 mt-2 items-center">

client/src/app/components/environments/environment-list/environment-list-view.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import { DeploymentStateTagComponent } from '../deployment-state-tag/deployment-
2525
import { LockTagComponent } from '../lock-tag/lock-tag.component';
2626
import { LockTimeComponent } from '../lock-time/lock-time.component';
2727
import { KeycloakService } from '@app/core/services/keycloak/keycloak.service';
28+
import { EnvironmentStatusInfoComponent } from '../environment-status-info/environment-status-info.component';
29+
import { EnvironmentStatusTagComponent } from '../environment-status-tag/environment-status-tag.component';
2830

2931
@Component({
3032
selector: 'app-environment-list-view',
@@ -37,7 +39,9 @@ import { KeycloakService } from '@app/core/services/keycloak/keycloak.service';
3739
IconsModule,
3840
ButtonModule,
3941
DeploymentStateTagComponent,
42+
EnvironmentStatusTagComponent,
4043
EnvironmentDeploymentInfoComponent,
44+
EnvironmentStatusInfoComponent,
4145
LockTimeComponent,
4246
ConfirmDialogModule,
4347
CommonModule,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<div class="flex flex-col">
2+
<span class="text-xs uppercase tracking-tighter font-bold text-gray-500 mb-2">Latest status check</span>
3+
4+
<div class="flex items-center justify-between">
5+
<span class="text-sm font-medium text-gray-700">Last Checked:</span>
6+
<span class="text-sm text-gray-500">
7+
{{ status().checkedAt | timeAgo }}
8+
</span>
9+
</div>
10+
<div class="flex items-center justify-between mt-1">
11+
<span class="text-sm font-medium text-gray-700">Status Code:</span>
12+
<span class="text-sm text-gray-500">
13+
{{ status().httpStatusCode || 'N/A' }}
14+
</span>
15+
</div>
16+
17+
@if (status().checkType === 'ARTEMIS_INFO') {
18+
<span class="text-xs uppercase tracking-tighter text-gray-500 mt-3">Artemis Build</span>
19+
@for (item of artemisBuildInfo(); track item.label) {
20+
<div class="flex items-center justify-between mt-1">
21+
<span class="text-sm font-medium text-gray-700">{{ item.label }}:</span>
22+
<span class="text-sm text-gray-500">{{ item.value || '-/-' }}</span>
23+
</div>
24+
}
25+
}
26+
</div>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Component, computed, inject, input } from '@angular/core';
2+
import { EnvironmentStatusDto } from '@app/core/modules/openapi';
3+
import { DateService } from '@app/core/services/date.service';
4+
import { TimeAgoPipe } from '@app/pipes/time-ago.pipe';
5+
6+
@Component({
7+
selector: 'app-environment-status-info',
8+
imports: [TimeAgoPipe],
9+
templateUrl: './environment-status-info.component.html',
10+
})
11+
export class EnvironmentStatusInfoComponent {
12+
status = input.required<EnvironmentStatusDto>();
13+
dateService = inject(DateService);
14+
15+
artemisBuildInfo = computed<{ label: string; value?: string }[]>(() => {
16+
const status = this.status();
17+
const metadata = status.metadata as
18+
| {
19+
name?: string;
20+
group?: string;
21+
version?: string;
22+
buildTime?: number;
23+
}
24+
| undefined;
25+
26+
return [
27+
{
28+
label: 'Name',
29+
value: metadata?.name,
30+
},
31+
{
32+
label: 'Group',
33+
value: metadata?.group,
34+
},
35+
{
36+
label: 'Version',
37+
value: metadata?.version,
38+
},
39+
{
40+
label: 'Build Time',
41+
value: metadata?.buildTime ? this.dateService.formatDate(metadata.buildTime * 1000, 'yyyy-MM-dd HH:mm:ss') || '-/-' : undefined,
42+
},
43+
];
44+
});
45+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@if (status(); as status) {
2+
<!-- Info State -->
3+
<!-- Success State -->
4+
@if (status.success) {
5+
<p-tag severity="success" [rounded]="true">
6+
<i-tabler name="check" class="!h-4 !w-4 mr-0.5"></i-tabler>
7+
Status check successful
8+
</p-tag>
9+
} @else {
10+
<!-- Error State -->
11+
<p-tag severity="danger" [rounded]="true">
12+
<i-tabler name="exclamation-circle" class="!h-4 !w-4 mr-0.5"></i-tabler>
13+
Status check failed
14+
</p-tag>
15+
}
16+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Component, input } from '@angular/core';
2+
import { EnvironmentStatusDto } from '@app/core/modules/openapi';
3+
import { IconsModule } from 'icons.module';
4+
import { TagModule } from 'primeng/tag';
5+
6+
@Component({
7+
selector: 'app-environment-status-tag',
8+
imports: [TagModule, IconsModule],
9+
templateUrl: './environment-status-tag.component.html',
10+
})
11+
export class EnvironmentStatusTagComponent {
12+
status = input.required<EnvironmentStatusDto>();
13+
}

client/src/app/components/forms/environment-edit-form/environment-edit-form.component.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@
2323
<p-autoComplete id="installed-apps" formControlName="installedApps" [multiple]="true" [minLength]="1" fluid [typeahead]="false"> </p-autoComplete>
2424
</div>
2525

26+
<div class="flex flex-col gap-1">
27+
<label for="status-check-type">Status Check</label>
28+
<span class="text-sm text-gray-500"> When enabled, the status check will run periodically to check the health of the environment. </span>
29+
<p-select id="status-check-type" class="mt-2" [options]="statusCheckTypes" formControlName="statusCheckType"></p-select>
30+
</div>
31+
32+
@if (environmentForm.get('statusCheckType')?.value !== null) {
33+
<div class="flex flex-col gap-2">
34+
<label for="status-check-url">Status URL</label>
35+
<span class="text-sm text-gray-500">
36+
This can be the URL of the Artemis management info endpoint (if set as the status check type) or any other URL that returns a 200 status code when the environment is
37+
healthy.
38+
</span>
39+
<input pInputText id="status-check-url" type="text" formControlName="statusUrl" />
40+
</div>
41+
}
42+
2643
<!-- Environment Enable/Disable Checkbox -->
2744
<div class="flex gap-2">
2845
<label for="enabled" class="mr-2">Enabled</label>

client/src/app/components/forms/environment-edit-form/environment-edit-form.component.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, computed, effect, inject, input, OnInit } from '@angular/core';
1+
import { Component, computed, inject, input, OnInit } from '@angular/core';
22
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
33
import { ButtonModule } from 'primeng/button';
44
import { InputSwitchModule } from 'primeng/inputswitch';
@@ -14,10 +14,12 @@ import {
1414
} from '@app/core/modules/openapi/@tanstack/angular-query-experimental.gen';
1515
import { MessageService } from 'primeng/api';
1616
import { Checkbox } from 'primeng/checkbox';
17+
import { SelectModule } from 'primeng/select';
18+
import { toObservable } from '@angular/core/rxjs-interop';
1719

1820
@Component({
1921
selector: 'app-environment-edit-form',
20-
imports: [AutoCompleteModule, ReactiveFormsModule, InputTextModule, InputSwitchModule, ButtonModule, Checkbox],
22+
imports: [AutoCompleteModule, ReactiveFormsModule, InputTextModule, InputSwitchModule, ButtonModule, Checkbox, SelectModule],
2123
templateUrl: './environment-edit-form.component.html',
2224
})
2325
export class EnvironmentEditFormComponent implements OnInit {
@@ -27,15 +29,26 @@ export class EnvironmentEditFormComponent implements OnInit {
2729
private route = inject(ActivatedRoute);
2830

2931
constructor(private messageService: MessageService) {
30-
// This effect is needed, because the form is initialized before the data is fetched.
32+
// This subscription is needed, because the form is initialized before the data is fetched.
3133
// As soon as the data is fetched, the form is updated with the fetched data.
32-
effect(() => {
33-
if (this.environment()) {
34-
this.environmentForm.patchValue(this.environment() || {});
34+
//
35+
// We used to call effect() here, but selecting an option within NgPrime Select
36+
// for some reason triggers the effect to run and reset the form to the initial state.
37+
toObservable(this.environment).subscribe(environment => {
38+
if (!environment) {
39+
return;
3540
}
41+
42+
this.environmentForm.patchValue(environment);
3643
});
3744
}
3845

46+
statusCheckTypes = [
47+
{ label: 'Disabled', value: null },
48+
{ label: 'HTTP Status', value: 'HTTP_STATUS' },
49+
{ label: 'Artemis Info', value: 'ARTEMIS_INFO' },
50+
];
51+
3952
environmentId = input<number>(0); // This is the environment id
4053
environmentForm!: FormGroup;
4154

@@ -64,12 +77,16 @@ export class EnvironmentEditFormComponent implements OnInit {
6477
environment = computed(() => this.environmentQuery.data());
6578

6679
ngOnInit(): void {
80+
const environment = this.environment();
81+
6782
this.environmentForm = this.formBuilder.group({
68-
name: [this.environment()?.name || '', Validators.required],
69-
installedApps: [this.environment()?.installedApps || []],
70-
description: [this.environment()?.description || ''],
71-
serverUrl: [this.environment()?.serverUrl || ''],
72-
enabled: [this.environment()?.enabled || false],
83+
name: [environment?.name || '', Validators.required],
84+
installedApps: [environment?.installedApps || []],
85+
description: [environment?.description || ''],
86+
serverUrl: [environment?.serverUrl || ''],
87+
enabled: [environment?.enabled || false],
88+
statusCheckType: [environment?.statusCheckType || null],
89+
statusUrl: [environment?.statusUrl || ''],
7390
});
7491
}
7592

@@ -79,7 +96,10 @@ export class EnvironmentEditFormComponent implements OnInit {
7996

8097
submitForm = () => {
8198
if (this.environmentForm && this.environmentForm.valid) {
82-
this.mutateEnvironment.mutate({ path: { id: this.environmentId() }, body: this.environmentForm.value });
99+
this.mutateEnvironment.mutate({
100+
path: { id: this.environmentId() },
101+
body: this.environmentForm.value,
102+
});
83103
}
84104
};
85105
}

client/src/app/core/modules/openapi/schemas.gen.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,19 @@ export const EnvironmentDtoSchema = {
122122
serverUrl: {
123123
type: 'string',
124124
},
125+
statusCheckType: {
126+
type: 'string',
127+
enum: ['HTTP_STATUS', 'ARTEMIS_INFO'],
128+
},
129+
statusUrl: {
130+
type: 'string',
131+
},
125132
latestDeployment: {
126133
$ref: '#/components/schemas/EnvironmentDeployment',
127134
},
135+
latestStatus: {
136+
$ref: '#/components/schemas/EnvironmentStatusDto',
137+
},
128138
lockedBy: {
129139
type: 'string',
130140
},
@@ -136,6 +146,36 @@ export const EnvironmentDtoSchema = {
136146
required: ['id', 'name'],
137147
} as const;
138148

149+
export const EnvironmentStatusDtoSchema = {
150+
type: 'object',
151+
properties: {
152+
id: {
153+
type: 'integer',
154+
format: 'int64',
155+
},
156+
success: {
157+
type: 'boolean',
158+
},
159+
httpStatusCode: {
160+
type: 'integer',
161+
format: 'int32',
162+
},
163+
checkedAt: {
164+
type: 'string',
165+
format: 'date-time',
166+
},
167+
checkType: {
168+
type: 'string',
169+
enum: ['HTTP_STATUS', 'ARTEMIS_INFO'],
170+
},
171+
metadata: {
172+
type: 'object',
173+
additionalProperties: {},
174+
},
175+
},
176+
required: ['checkType', 'checkedAt', 'httpStatusCode', 'id', 'success'],
177+
} as const;
178+
139179
export const RepositoryInfoDtoSchema = {
140180
type: 'object',
141181
properties: {

client/src/app/core/modules/openapi/types.gen.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,23 @@ export type EnvironmentDto = {
3737
installedApps?: Array<string>;
3838
description?: string;
3939
serverUrl?: string;
40+
statusCheckType?: 'HTTP_STATUS' | 'ARTEMIS_INFO';
41+
statusUrl?: string;
4042
latestDeployment?: EnvironmentDeployment;
43+
latestStatus?: EnvironmentStatusDto;
4144
lockedBy?: string;
4245
lockedAt?: string;
4346
};
4447

48+
export type EnvironmentStatusDto = {
49+
id: number;
50+
success: boolean;
51+
httpStatusCode: number;
52+
checkedAt: string;
53+
checkType: 'HTTP_STATUS' | 'ARTEMIS_INFO';
54+
metadata?: {};
55+
};
56+
4557
export type RepositoryInfoDto = {
4658
id: number;
4759
name: string;

client/src/app/core/services/date.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { inject, Injectable } from '@angular/core';
77
export class DateService {
88
private datePipe = inject(DatePipe);
99

10-
formatDate = (date?: string, formatString: string = 'd. MMMM y'): string | null => {
10+
formatDate = (date?: Date | string | number, formatString: string = 'd. MMMM y'): string | null => {
1111
return date ? this.datePipe.transform(date, formatString) : null;
1212
};
1313
}

client/src/main.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ Sentry.init({
88
enabled: environment.sentry.enabled,
99
// The DSN (Data Source Name) tells the SDK where to send the events to.
1010
dsn: environment.sentry.dsn,
11-
debug: true,
1211
// The browser tracing integration captures performance data
1312
// like throughput and latency
1413
integrations: [Sentry.browserTracingIntegration()],

0 commit comments

Comments
 (0)