fix: pull requests deployments

feat: filter deployments logs by pull requests
This commit is contained in:
Andras Bacsai 2023-11-01 15:39:47 +01:00
parent be8ea78b1b
commit 4520070df3
8 changed files with 124 additions and 88 deletions

View File

@ -41,7 +41,7 @@ class ApplicationController extends Controller
if (!$application) { if (!$application) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 8); ['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 40);
return view('project.application.deployments', ['application' => $application, 'deployments' => $deployments, 'deployments_count' => $count]); return view('project.application.deployments', ['application' => $application, 'deployments' => $deployments, 'deployments_count' => $count]);
} }

View File

@ -3,24 +3,31 @@
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Application;
use App\Models\Application; use App\Models\Application;
use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
class Deployments extends Component class Deployments extends Component
{ {
public Application $application; public Application $application;
public $deployments = []; public Array|Collection $deployments = [];
public int $deployments_count = 0; public int $deployments_count = 0;
public string $current_url; public string $current_url;
public int $skip = 0; public int $skip = 0;
public int $default_take = 8; public int $default_take = 40;
public bool $show_next = false; public bool $show_next = false;
public ?string $pull_request_id = null;
protected $queryString = ['pull_request_id'];
public function mount() public function mount()
{ {
$this->current_url = url()->current(); $this->current_url = url()->current();
$this->show_pull_request_only();
$this->show_more(); $this->show_more();
} }
private function show_pull_request_only() {
if ($this->pull_request_id) {
$this->deployments = $this->deployments->where('pull_request_id', $this->pull_request_id);
}
}
private function show_more() private function show_more()
{ {
if (count($this->deployments) !== 0) { if (count($this->deployments) !== 0) {
@ -47,6 +54,7 @@ class Deployments extends Component
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take); ['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
$this->deployments = $deployments; $this->deployments = $deployments;
$this->deployments_count = $count; $this->deployments_count = $count;
$this->show_pull_request_only();
$this->show_more(); $this->show_more();
} }
} }

View File

@ -487,9 +487,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
private function deploy_pull_request() private function deploy_pull_request()
{ {
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build"); $this->generate_image_names();
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
$this->execute_remote_command([ $this->execute_remote_command([
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'", "echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
]); ]);
@ -505,7 +503,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// $this->generate_build_env_variables(); // $this->generate_build_env_variables();
// $this->add_build_env_variables_to_dockerfile(); // $this->add_build_env_variables_to_dockerfile();
$this->build_image(); $this->build_image();
$this->stop_running_container(); if ($this->currently_running_container_name) {
$this->execute_remote_command(
["echo -n 'Removing old version of your application.'"],
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
);
}
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Starting preview deployment.'"], ["echo -n 'Starting preview deployment.'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
@ -743,6 +746,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} else { } else {
$labels = collect(generateLabelsApplication($this->application, $this->preview)); $labels = collect(generateLabelsApplication($this->application, $this->preview));
} }
if ($this->pull_request_id !== 0) {
$labels = $labels->reject(function ($label) {
return str($label)->contains('Host');
});
$newLabels = collect(generateLabelsApplication($this->application, $this->preview));
$hostLabels = $newLabels->filter(function ($label) {
return str($label)->contains('Host');
});
$labels = $labels->merge($hostLabels);
}
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray(); $labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
$docker_compose = [ $docker_compose = [
'version' => '3.8', 'version' => '3.8',

View File

@ -138,11 +138,10 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
$containerStatus = "$containerStatus ($containerHealth)"; $containerStatus = "$containerStatus ($containerHealth)";
$labels = data_get($container, 'Config.Labels'); $labels = data_get($container, 'Config.Labels');
$labels = Arr::undot(format_docker_labels_to_json($labels)); $labels = Arr::undot(format_docker_labels_to_json($labels));
$labelId = data_get($labels, 'coolify.applicationId'); $applicationId = data_get($labels, 'coolify.applicationId');
if ($labelId) { if ($applicationId) {
if (str_contains($labelId, '-pr-')) { $pullRequestId = data_get($labels, 'coolify.pullRequestId');
$pullRequestId = data_get($labels, 'coolify.pullRequestId'); if ($pullRequestId) {
$applicationId = (int) Str::before($labelId, '-pr-');
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first(); $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
if ($preview) { if ($preview) {
$foundApplicationPreviews[] = $preview->id; $foundApplicationPreviews[] = $preview->id;
@ -154,7 +153,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
//Notify user that this container should not be there. //Notify user that this container should not be there.
} }
} else { } else {
$application = $applications->where('id', $labelId)->first(); $application = $applications->where('id', $applicationId)->first();
if ($application) { if ($application) {
$foundApplications[] = $application->id; $foundApplications[] = $application->id;
$statusFromDb = $application->status; $statusFromDb = $application->status;

View File

@ -16,14 +16,14 @@
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif <div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
class="relative flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto text-xs border border-dotted rounded scrollbar border-coolgray-400" class="relative flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto text-xs border border-dotted rounded scrollbar border-coolgray-400"
:class="fullscreen ? '' : 'max-h-[32rem]'"> :class="fullscreen ? '' : 'max-h-[32rem]'">
<button title="Minimize" x-show="fullscreen" class="absolute top-2 right-2" <button title="Minimize" x-show="fullscreen" class="fixed top-2 right-2"
x-on:click="fullscreen = !fullscreen"><svg class="icon" viewBox="0 0 24 24" x-on:click="fullscreen = !fullscreen"><svg class="icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" /> stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg></button> </svg></button>
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-2" <button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8"
x-on:click="fullscreen = !fullscreen"><svg class="icon" viewBox="0 0 24 24" x-on:click="fullscreen = !fullscreen"><svg class="fixed icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
<g fill="none"> <g fill="none">
<path <path

View File

@ -1,10 +1,17 @@
<div class="flex flex-col gap-2" @if ($skip == 0) wire:poll.5000ms='reload_deployments' @endif> <div class="flex flex-col gap-2 pb-10" @if ($skip == 0) wire:poll.5000ms='reload_deployments' @endif>
<h2 class="pt-4">Deployments <span class="text-xs">({{ $deployments_count }})</span></h2> <div class="flex items-end gap-2 pt-4">
@if ($show_next) <h2>Deployments <span class="text-xs">({{ $deployments_count }})</span></h2>
<x-forms.button wire:click="load_deployments({{ $default_take }})">Show More @if ($show_next)
</x-forms.button> <x-forms.button wire:click="load_deployments({{ $default_take }})">Next Page
@endif </x-forms.button>
@foreach ($deployments as $deployment) @endif
</div>
<form wire:submit="filter" class="flex items-end gap-2">
<x-forms.input id="pull_request_id" label="Pull Request"></x-forms.input>
<x-forms.button type="submit">Filter</x-forms.button>
</form>
@forelse ($deployments as $deployment)
<a @class([ <a @class([
'bg-coolgray-200 p-2 border-l border-dashed transition-colors hover:no-underline', 'bg-coolgray-200 p-2 border-l border-dashed transition-colors hover:no-underline',
'cursor-not-allowed hover:bg-coolgray-200' => 'cursor-not-allowed hover:bg-coolgray-200' =>
@ -22,73 +29,76 @@
class="hover:no-underline"> class="hover:no-underline">
<div class="flex flex-col justify-start"> <div class="flex flex-col justify-start">
<div> <div>
{{ $deployment->id }} <span class=" text-warning">></span> {{ $deployment->deployment_uuid }} {{ $deployment->created_at }} UTC
<span class=" text-warning">></span> <span class=" text-warning">></span>
{{ $deployment->status }} {{ $deployment->status }}
</div> @if (data_get($deployment, 'pull_request_id'))
@if (data_get($deployment, 'pull_request_id')) <span class=" text-warning">></span>
<div>
Pull Request #{{ data_get($deployment, 'pull_request_id') }} Pull Request #{{ data_get($deployment, 'pull_request_id') }}
@if (data_get($deployment, 'is_webhook')) @if (data_get($deployment, 'is_webhook'))
(Webhook) (Webhook)
@endif @endif
</div> @elseif (data_get($deployment, 'is_webhook'))
@elseif (data_get($deployment, 'is_webhook')) <span class=" text-warning">></span>
<div>Webhook (sha </div>
@if (data_get($deployment, 'commit')) Webhook (sha
{{ data_get($deployment, 'commit') }}) @if (data_get($deployment, 'commit'))
@else {{ data_get($deployment, 'commit') }})
HEAD) @else
@endif HEAD)
</div>
@endif @endif
<div class="flex flex-col" x-data="elapsedTime('{{ $deployment->deployment_uuid }}', '{{ $deployment->status }}', '{{ $deployment->created_at }}', '{{ $deployment->updated_at }}')"> @endif
<div> </div>
@if ($deployment->status !== 'in_progress')
Finished <span x-text="measure_since_started()">0s</span> in <div class="flex flex-col" x-data="elapsedTime('{{ $deployment->deployment_uuid }}', '{{ $deployment->status }}', '{{ $deployment->created_at }}', '{{ $deployment->updated_at }}')">
@else <div>
Running for @if ($deployment->status !== 'in_progress')
@endif Finished <span x-text="measure_since_started()">0s</span> in
<span class="font-bold" x-text="measure_finished_time()">0s</span> @else
</div> Running for
@endif
<span class="font-bold" x-text="measure_finished_time()">0s</span>
</div> </div>
</div> </div>
</a> </div>
@endforeach </a>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script> @empty
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.js"></script> <div class="">No deployments found</div>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/relativeTime.js"></script> @endforelse
<script> <script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
document.addEventListener('alpine:init', () => { <script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.js"></script>
let timers = {}; <script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/relativeTime.js"></script>
<script>
dayjs.extend(window.dayjs_plugin_utc); document.addEventListener('alpine:init', () => {
dayjs.extend(window.dayjs_plugin_relativeTime); let timers = {};
Alpine.data('elapsedTime', (uuid, status, created_at, updated_at) => ({ dayjs.extend(window.dayjs_plugin_utc);
finished_time: 'calculating...', dayjs.extend(window.dayjs_plugin_relativeTime);
started_time: 'calculating...',
init() { Alpine.data('elapsedTime', (uuid, status, created_at, updated_at) => ({
if (timers[uuid]) { finished_time: 'calculating...',
clearInterval(timers[uuid]); started_time: 'calculating...',
} init() {
if (status === 'in_progress') { if (timers[uuid]) {
timers[uuid] = setInterval(() => { clearInterval(timers[uuid]);
this.finished_time = dayjs().diff(dayjs.utc(created_at), }
'second') + 's' if (status === 'in_progress') {
}, 1000); timers[uuid] = setInterval(() => {
} else { this.finished_time = dayjs().diff(dayjs.utc(created_at),
let seconds = dayjs.utc(updated_at).diff(dayjs.utc(created_at), 'second') 'second') + 's'
this.finished_time = seconds + 's'; }, 1000);
} } else {
}, let seconds = dayjs.utc(updated_at).diff(dayjs.utc(created_at), 'second')
measure_finished_time() { this.finished_time = seconds + 's';
return this.finished_time; }
}, },
measure_since_started() { measure_finished_time() {
return dayjs.utc(created_at).fromNow(); return this.finished_time;
} },
})) measure_since_started() {
}) return dayjs.utc(created_at).fromNow();
</script> }
}))
})
</script>
</div> </div>

View File

@ -83,6 +83,12 @@
Preview Preview
</x-forms.button> </x-forms.button>
@endif @endif
<a
href="{{ route('project.application.deployments', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}">
<x-forms.button>
Get Deployment Logs
</x-forms.button>
</a>
</div> </div>
</div> </div>
@endforeach @endforeach

View File

@ -17,13 +17,13 @@
<div x-data="{ fullscreen: false }" :class="fullscreen ? 'fullscreen' : 'container w-full pt-4 mx-auto'"> <div x-data="{ fullscreen: false }" :class="fullscreen ? 'fullscreen' : 'container w-full pt-4 mx-auto'">
<div <div
class="relative flex flex-col-reverse w-full p-4 pt-6 overflow-y-auto text-xs text-white border border-solid rounded scrollbar border-coolgray-300" :class="fullscreen ? '' : 'max-h-[32rem]'"> class="relative flex flex-col-reverse w-full p-4 pt-6 overflow-y-auto text-xs text-white border border-solid rounded scrollbar border-coolgray-300" :class="fullscreen ? '' : 'max-h-[32rem]'">
<button title="Minimize" x-show="fullscreen" class="absolute top-2 right-2" x-on:click="fullscreen = !fullscreen"><svg <button title="Minimize" x-show="fullscreen" class="fixed top-2 right-2" x-on:click="fullscreen = !fullscreen"><svg
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" /> stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg></button> </svg></button>
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-2" x-on:click="fullscreen = !fullscreen"><svg <button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8" x-on:click="fullscreen = !fullscreen"><svg
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> class="fixed icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none"> <g fill="none">
<path <path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" /> d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />