Skip to content

Commit

Permalink
Merge branch 'staging' into feat/deployment-progress-bar
Browse files Browse the repository at this point in the history
  • Loading branch information
TurkerKoc committed Feb 17, 2025
2 parents b94ab55 + bdaf33e commit 62e7ad0
Show file tree
Hide file tree
Showing 91 changed files with 3,550 additions and 1,527 deletions.
4 changes: 4 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ application-server:
- changed-files:
- any-glob-to-any-file: server/application-server/**

migration-script:
- changed-files:
- any-glob-to-any-file: server/application-server/src/main/resources/db/migration/**

notification-server:
- changed-files:
- any-glob-to-any-file: server/notification/**
Expand Down
45 changes: 45 additions & 0 deletions .github/workflows/mock-staging-deployment-helios.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Mock Artemis Staging Deployment with Helios

on:
workflow_dispatch:
inputs:
branch_name:
description: 'Branch to deploy'
required: true
commit_sha:
description: 'Commit SHA to deploy'
required: true
environment_name:
description: 'Environment to deploy to'
required: true
type: choice
options:
- artemis-staging-localci.artemis.cit.tum.de

concurrency: ${{ github.event.inputs.environment_name }}

env:
build_workflow_name: build.yml

jobs:
check-build-status:
runs-on: ubuntu-latest
steps:
- name: Print inputs
run: |
echo "Branch: ${{ github.event.inputs.branch_name }}"
echo "Commit SHA: ${{ github.event.inputs.commit_sha }}"
echo "Environment: ${{ github.event.inputs.environment_name }}"
deploy:
needs: check-build-status
runs-on: ubuntu-latest
environment:
name: ${{ github.event.inputs.environment_name }}

steps:
- name: Fake deployment
run: |
echo "Start deployment to ${{ github.event.inputs.environment_name }}"
sleep 30
echo "Finish deployment"
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<div class="flex flex-col">
<span class="text-xs uppercase tracking-tighter font-bold text-gray-500 mb-2">Latest status check</span>

<div class="flex items-center justify-between">
<div class="flex items-center justify-between gap-1">
<span class="text-sm font-medium text-gray-700">Last Checked:</span>
<span class="text-sm text-gray-500">
{{ status().checkedAt | timeAgo }}
{{ timeSinceChecked() }}
</span>
</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center justify-between mt-1 gap-1">
<span class="text-sm font-medium text-gray-700">Status Code:</span>
<span class="text-sm text-gray-500">
{{ status().httpStatusCode || 'N/A' }}
Expand All @@ -17,7 +17,7 @@
@if (status().checkType === 'ARTEMIS_INFO') {
<span class="text-xs uppercase tracking-tighter text-gray-500 mt-3">Artemis Build</span>
@for (item of artemisBuildInfo(); track item.label) {
<div class="flex items-center justify-between mt-1">
<div class="flex items-center justify-between mt-1 gap-1">
<span class="text-sm font-medium text-gray-700">{{ item.label }}:</span>
<span class="text-sm text-gray-500">{{ item.value || '-/-' }}</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
import { Component, computed, inject, input } from '@angular/core';
import { Component, computed, inject, input, OnDestroy, OnInit, signal } from '@angular/core';
import { EnvironmentStatusDto } from '@app/core/modules/openapi';
import { DateService } from '@app/core/services/date.service';
import { TimeAgoPipe } from '@app/pipes/time-ago.pipe';

@Component({
selector: 'app-environment-status-info',
imports: [TimeAgoPipe],
providers: [TimeAgoPipe],
templateUrl: './environment-status-info.component.html',
})
export class EnvironmentStatusInfoComponent {
export class EnvironmentStatusInfoComponent implements OnInit, OnDestroy {
timeAgoPipe = inject(TimeAgoPipe);

status = input.required<EnvironmentStatusDto>();
dateService = inject(DateService);
checkedAt = computed(() => this.status().checkedAt);

// Using it as a pipe won't update the value
timeSinceChecked = computed(() => {
return this.timeAgoPipe.transform(this.checkedAt(), {
showSeconds: true,
referenceDate: this.timeNow(),
});
});

// track the current time in a signal that we update every second
timeNow = signal<Date>(new Date());

// store the interval ID so we can clear it later
private intervalId?: ReturnType<typeof setInterval>;

artemisBuildInfo = computed<{ label: string; value?: string }[]>(() => {
const status = this.status();
Expand All @@ -20,6 +37,7 @@ export class EnvironmentStatusInfoComponent {
group?: string;
version?: string;
buildTime?: number;
commitId?: string;
}
| undefined;

Expand All @@ -40,6 +58,24 @@ export class EnvironmentStatusInfoComponent {
label: 'Build Time',
value: metadata?.buildTime ? this.dateService.formatDate(metadata.buildTime * 1000, 'yyyy-MM-dd HH:mm:ss') || '-/-' : undefined,
},
{
label: 'Commit Hash',
value: metadata?.commitId ? metadata.commitId.slice(0, 7) : undefined,
},
];
});

ngOnInit() {
// Update timeNow every second
this.intervalId = setInterval(() => {
this.timeNow.set(new Date());
}, 1000);
}

ngOnDestroy() {
// Prevent memory leaks by clearing the interval
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
}
118 changes: 61 additions & 57 deletions client/src/app/components/pipeline/pipeline.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,65 +21,69 @@ <h3 class="text-xl mb-2">Pipeline</h3>
</div>
</p-panel>
</div>
}

@if (pipeline(); as pipeline) {
<div class="flex flex-wrap items-start gap-y-4 mt-2">
<!-- Check if all groups have no workflow runs -->
<!-- If so, display a message saying that no workflow runs have been found -->
@if (allGroupsHaveNoWorkflowRuns()) {
<div class="flex flex-col justify-center items-center p-4">
<i-tabler name="info-circle" class="!size-20 text-primary" />
<div class="font-semibold text-lg">No workflow runs found.</div>
<div class="text-sm">Workflow groups exist, but no runs have been executed yet.</div>
</div>
}
@for (group of pipeline.groups; track group.name) {
@if (group.workflows.length > 0) {
<ng-container>
<p-panel [header]="group.name" class="max-w-[250px]">
<div class="flex flex-col gap-4">
@for (workflowRun of group.workflows; track workflowRun.id) {
<div class="flex items-center space-x-2">
@if (workflowRun.status === 'COMPLETED') {
@switch (workflowRun.conclusion) {
@case ('SUCCESS') {
<i-tabler name="circle-check" class="text-green-500" pTooltip="Success"></i-tabler>
}
@case ('FAILURE') {
<i-tabler name="circle-x" class="text-red-500" pTooltip="Failure"></i-tabler>
}
@default {
} @else {
@if (pipeline(); as pipeline) {
<div class="flex flex-wrap items-start gap-y-4 mt-2">
<!-- Check if all groups have no workflow runs -->
<!-- If so, display a message saying that no workflow runs have been found -->
@if (allGroupsHaveNoWorkflowRuns()) {
<div class="flex flex-col justify-center items-center p-4">
<i-tabler name="info-circle" class="!size-20 text-primary" />
<div class="font-semibold text-lg">No workflow runs found.</div>
<div class="text-sm">Workflow groups exist, but no runs have been executed yet.</div>
</div>
}
@for (group of pipeline.groups; track group.name) {
@if (group.workflows.length > 0) {
<ng-container>
<p-panel [header]="group.name" class="max-w-[250px]">
<div class="flex flex-col gap-4">
@for (workflowRun of group.workflows; track workflowRun.id) {
<div class="flex items-center space-x-2">
<div class="flex items-center">
@if (workflowRun.status === 'COMPLETED') {
@switch (workflowRun.conclusion) {
@case ('SUCCESS') {
<i-tabler name="circle-check" class="text-green-500" pTooltip="Success"></i-tabler>
}
@case ('FAILURE') {
<i-tabler name="circle-x" class="text-red-500" pTooltip="Failure"></i-tabler>
}
@default {
<i-tabler name="progress-help" class="text-gray-500" pTooltip="Unknown"></i-tabler>
}
}
} @else if (['IN_PROGRESS', 'QUEUED', 'PENDING', 'WAITING', 'REQUESTED'].includes(workflowRun.status)) {
<i-tabler name="progress" class="text-yellow-500 animate-spin" pTooltip="In progress"></i-tabler>
} @else {
<i-tabler name="progress-help" class="text-gray-500" pTooltip="Unknown"></i-tabler>
}
}
} @else if (['IN_PROGRESS', 'QUEUED', 'PENDING', 'WAITING', 'REQUESTED'].includes(workflowRun.status)) {
<i-tabler name="progress" class="text-yellow-500 animate-spin" pTooltip="In progress"></i-tabler>
} @else {
<i-tabler name="progress-help" class="text-gray-500" pTooltip="Unknown"></i-tabler>
}
<!-- Fixed width to ensure consistent alignment -->
<span class="font-medium flex items-center justify-between w-full">
<span [pTooltip]="workflowRun.name">{{ workflowRun.name }}</span>
<a class="ml-1" [href]="workflowRun.htmlUrl" target="_new"><i-tabler name="external-link" class="w-20 h-20"></i-tabler></a>
</span>
</div>
}
</div>
</p-panel>
</div>
<!-- Fixed width to ensure consistent alignment -->
<span class="font-medium flex items-center justify-between w-full">
<span [pTooltip]="workflowRun.name">{{ workflowRun.name }}</span>
<a class="ml-1" [href]="workflowRun.htmlUrl" target="_new"><i-tabler name="external-link" class="w-20 h-20"></i-tabler></a>
</span>
</div>
}
</div>
</p-panel>

<!-- Connection line -->
@if (!group.isLastWithWorkflows) {
<div class="w-10 h-px bg-gray-300 mt-7"></div>
}
</ng-container>
<!-- Connection line -->
@if (!group.isLastWithWorkflows) {
<div class="w-10 h-px bg-gray-300 mt-7"></div>
}
</ng-container>
}
} @empty {
<div class="flex flex-col justify-center items-center p-4">
<i-tabler name="exclamation-circle" class="!size-20 text-primary" />
<div class="font-semibold text-lg">There are no pipeline groups yet.</div>
<div class="text-sm">Create one in the repository settings.</div>
</div>
}
} @empty {
<div class="flex flex-col justify-center items-center p-4">
<i-tabler name="exclamation-circle" class="!size-20 text-primary" />
<div class="font-semibold text-lg">There are no pipeline groups yet.</div>
<div class="text-sm">Create one in the repository settings.</div>
</div>
}
</div>
</div>

<app-pipeline-test-results [pipeline]="pipeline" />
}
}
19 changes: 15 additions & 4 deletions client/src/app/components/pipeline/pipeline.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
getGroupsWithWorkflowsOptions,
} from '@app/core/modules/openapi/@tanstack/angular-query-experimental.gen';
import { SkeletonModule } from 'primeng/skeleton';
import { WorkflowRunDto } from '@app/core/modules/openapi';
import { PipelineTestResultsComponent } from './test-results/pipeline-test-results.component';

export type PipelineSelector = { repositoryId: number } & (
| {
Expand All @@ -21,9 +23,18 @@ export type PipelineSelector = { repositoryId: number } & (
}
);

export interface Pipeline {
groups: {
name: string;
id: number;
workflows: WorkflowRunDto[];
isLastWithWorkflows: boolean;
}[];
}

@Component({
selector: 'app-pipeline',
imports: [TableModule, ProgressSpinnerModule, PanelModule, IconsModule, TooltipModule, SkeletonModule],
imports: [TableModule, ProgressSpinnerModule, PanelModule, IconsModule, TooltipModule, SkeletonModule, PipelineTestResultsComponent],
templateUrl: './pipeline.component.html',
})
export class PipelineComponent {
Expand All @@ -46,12 +57,12 @@ export class PipelineComponent {
});
//TODO instead of refetching every 15 seconds, we should use websockets to get real-time updates
branchQuery = injectQuery(() => ({
...getLatestWorkflowRunsByBranchAndHeadCommitOptions({ query: { branch: this.branchName()! } }),
...getLatestWorkflowRunsByBranchAndHeadCommitOptions({ query: { branch: this.branchName()!, includeTestSuites: true } }),
enabled: this.branchName() !== null,
refetchInterval: 15000,
}));
pullRequestQuery = injectQuery(() => ({
...getLatestWorkflowRunsByPullRequestIdAndHeadCommitOptions({ path: { pullRequestId: this.pullRequestId() || 0 } }),
...getLatestWorkflowRunsByPullRequestIdAndHeadCommitOptions({ path: { pullRequestId: this.pullRequestId() || 0 }, query: { includeTestSuites: true } }),
enabled: this.pullRequestId() !== null,
refetchInterval: 15000,
}));
Expand All @@ -61,7 +72,7 @@ export class PipelineComponent {
refetchInterval: 15000,
}));

pipeline = computed(() => {
pipeline = computed<Pipeline>(() => {
const workflowRuns = (this.branchName() ? this.branchQuery.data() : this.pullRequestQuery.data()) || [];
const workflowGroups = this.groupsQuery.data() || [];

Expand Down
Loading

0 comments on commit 62e7ad0

Please sign in to comment.