feat: working target history

This commit is contained in:
Tine 2024-05-25 13:45:25 +02:00
parent 034f530923
commit 315b7c1381
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
17 changed files with 162 additions and 94 deletions

View file

@ -0,0 +1,28 @@
package activities
import (
"context"
"github.com/mentos1386/zdravko/database/models"
"github.com/mentos1386/zdravko/internal/server/services"
"github.com/mentos1386/zdravko/internal/temporal"
)
func (a *Activities) AddTargetHistory(ctx context.Context, param temporal.ActivityAddTargetHistoryParam) (*temporal.ActivityAddTargetHistoryResult, error) {
status := models.TargetStatusUnknown
if param.Status == temporal.AddTargetHistoryStatusSuccess {
status = models.TargetStatusSuccess
}
if param.Status == temporal.AddTargetHistoryStatusFailure {
status = models.TargetStatusFailure
}
err := services.AddHistoryForTarget(ctx, a.db, &models.TargetHistory{
TargetId: param.Target.Id,
Status: status,
Note: param.Note,
})
return &temporal.ActivityAddTargetHistoryResult{}, err
}

View file

@ -1,11 +0,0 @@
package activities
import (
"context"
"github.com/mentos1386/zdravko/internal/temporal"
)
func (a *Activities) ProcessCheckOutcome(ctx context.Context, param temporal.ActivityProcessCheckOutcomeParam) (*temporal.ActivityProcessCheckOutcomeResult, error) {
return nil, nil
}

View file

@ -37,10 +37,12 @@ func (a *Activities) TargetsFilter(ctx context.Context, param temporal.ActivityT
a.logger.Info("TargetsFilter", "target", target) a.logger.Info("TargetsFilter", "target", target)
targetWithMedatada := &struct { targetWithMedatada := &struct {
Id string
Name string Name string
Group string Group string
Metadata map[string]interface{} Metadata map[string]interface{}
}{ }{
Id: target.Id,
Name: target.Name, Name: target.Name,
Group: target.Group, Group: target.Group,
Metadata: metadata, Metadata: metadata,
@ -57,6 +59,7 @@ func (a *Activities) TargetsFilter(ctx context.Context, param temporal.ActivityT
} }
if value.Export().(bool) { if value.Export().(bool) {
filteredTargets = append(filteredTargets, &temporal.Target{ filteredTargets = append(filteredTargets, &temporal.Target{
Id: target.Id,
Name: target.Name, Name: target.Name,
Group: target.Group, Group: target.Group,
Metadata: target.Metadata, Metadata: target.Metadata,

View file

@ -85,7 +85,7 @@ filter: |
target: | target: |
kind: Http kind: Http
tags: labels:
production: "true" production: "true"
spec: spec:
url: "https://test.k6.io" url: "https://test.k6.io"

View file

@ -6,53 +6,71 @@ import (
"time" "time"
"github.com/mentos1386/zdravko/internal/temporal" "github.com/mentos1386/zdravko/internal/temporal"
"github.com/mentos1386/zdravko/pkg/api"
"go.temporal.io/sdk/workflow" "go.temporal.io/sdk/workflow"
) )
func (w *Workflows) CheckWorkflowDefinition(ctx workflow.Context, param temporal.WorkflowCheckParam) (api.CheckStatus, error) { func (w *Workflows) CheckWorkflowDefinition(ctx workflow.Context, param temporal.WorkflowCheckParam) error {
workerGroupIds := param.WorkerGroupIds workerGroupIds := param.WorkerGroupIds
sort.Strings(workerGroupIds) sort.Strings(workerGroupIds)
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: 60 * time.Second,
TaskQueue: temporal.TEMPORAL_SERVER_QUEUE,
})
targetsFilterParam := temporal.ActivityTargetsFilterParam{
Filter: param.Filter,
}
targetsFilterResult := temporal.ActivityTargetsFilterResult{} targetsFilterResult := temporal.ActivityTargetsFilterResult{}
err := workflow.ExecuteActivity(ctx, temporal.ActivityTargetsFilterName, targetsFilterParam).Get(ctx, &targetsFilterResult) err := workflow.ExecuteActivity(
workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: 60 * time.Second,
TaskQueue: temporal.TEMPORAL_SERVER_QUEUE,
}),
temporal.ActivityTargetsFilterName,
temporal.ActivityTargetsFilterParam{
Filter: param.Filter,
},
).Get(ctx, &targetsFilterResult)
if err != nil { if err != nil {
return api.CheckStatusUnknown, err return err
} }
for _, target := range targetsFilterResult.Targets { for _, target := range targetsFilterResult.Targets {
for _, workerGroupId := range workerGroupIds { for _, workerGroupId := range workerGroupIds {
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: 60 * time.Second,
TaskQueue: workerGroupId,
})
heatlcheckParam := temporal.ActivityCheckParam{
Script: param.Script,
Target: target,
}
var checkResult *temporal.ActivityCheckResult var checkResult *temporal.ActivityCheckResult
err := workflow.ExecuteActivity(ctx, temporal.ActivityCheckName, heatlcheckParam).Get(ctx, &checkResult) err := workflow.ExecuteActivity(
workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: 60 * time.Second,
TaskQueue: workerGroupId,
}),
temporal.ActivityCheckName,
temporal.ActivityCheckParam{
Script: param.Script,
Target: target,
},
).Get(ctx, &checkResult)
if err != nil { if err != nil {
return api.CheckStatusUnknown, err return err
} }
status := api.CheckStatusFailure status := temporal.AddTargetHistoryStatusFailure
if checkResult.Success { if checkResult.Success {
status = api.CheckStatusSuccess status = temporal.AddTargetHistoryStatusSuccess
}
var addTargetHistoryResult *temporal.ActivityAddTargetHistoryResult
err = workflow.ExecuteActivity(
workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: 60 * time.Second,
TaskQueue: temporal.TEMPORAL_SERVER_QUEUE,
}),
temporal.ActivityAddTargetHistoryName,
&temporal.ActivityAddTargetHistoryParam{
Target: target,
Status: status,
Note: checkResult.Note,
},
).Get(ctx, &addTargetHistoryResult)
if err != nil {
return err
} }
slog.Info("Check %s status: %s", param.CheckId, status) slog.Info("Check %s status: %s", param.CheckId, status)
} }
} }
return api.CheckStatusSuccess, nil return nil
} }

View file

@ -0,0 +1,20 @@
package temporal
type AddTargetHistoryStatus string
const (
AddTargetHistoryStatusSuccess AddTargetHistoryStatus = "SUCCESS"
AddTargetHistoryStatusFailure AddTargetHistoryStatus = "FAILURE"
AddTargetHistoryStatusUnknown AddTargetHistoryStatus = "UNKNOWN"
)
type ActivityAddTargetHistoryParam struct {
Target *Target
Status AddTargetHistoryStatus
Note string
}
type ActivityAddTargetHistoryResult struct {
}
const ActivityAddTargetHistoryName = "ADD_TARGET_HISTORY"

View file

@ -1,10 +0,0 @@
package temporal
type ActivityProcessCheckOutcomeParam struct {
Outcome string
}
type ActivityProcessCheckOutcomeResult struct {
}
const ActivityProcessCheckOutcomeName = "PROCESS_CHECK_OUTCOME"

View file

@ -13,6 +13,7 @@ import (
) )
type Target struct { type Target struct {
Id string
Name string Name string
Group string Group string
Metadata string Metadata string

View file

@ -1,15 +1 @@
package api package api
type CheckStatus string
const (
CheckStatusSuccess CheckStatus = "SUCCESS"
CheckStatusFailure CheckStatus = "FAILURE"
CheckStatusUnknown CheckStatus = "UNKNOWN"
)
type ApiV1ChecksHistoryPOSTBody struct {
Status CheckStatus `json:"status"`
Note string `json:"note"`
WorkerGroupId string `json:"worker_group"`
}

View file

@ -31,7 +31,7 @@ func NewWorker(temporalClient client.Client, cfg *config.ServerConfig, logger *s
// Register Activities // Register Activities
worker.RegisterActivityWithOptions(a.TargetsFilter, activity.RegisterOptions{Name: temporal.ActivityTargetsFilterName}) worker.RegisterActivityWithOptions(a.TargetsFilter, activity.RegisterOptions{Name: temporal.ActivityTargetsFilterName})
worker.RegisterActivityWithOptions(a.ProcessCheckOutcome, activity.RegisterOptions{Name: temporal.ActivityProcessCheckOutcomeName}) worker.RegisterActivityWithOptions(a.AddTargetHistory, activity.RegisterOptions{Name: temporal.ActivityAddTargetHistoryName})
return &Worker{ return &Worker{
worker: worker, worker: worker,

View file

@ -1798,6 +1798,10 @@ code {
} }
@media (min-width: 640px) { @media (min-width: 640px) {
.sm\:col-span-2 {
grid-column: span 2 / span 2;
}
.sm\:w-auto { .sm\:w-auto {
width: auto; width: auto;
} }

View file

@ -35,26 +35,26 @@
<code>@yearly</code>. <code>@yearly</code>.
</p> </p>
<label for="filter">Filter</label> <label for="filter">Filter</label>
<textarea required id="filter" name="filter" class="h-12"> <textarea required id="filter" name="filter" class="sm:col-span-2 h-12">
{{ ScriptUnescapeString .ExampleFilter }}</textarea {{ ScriptUnescapeString .ExampleFilter }}</textarea
> >
<div <div
id="editor-filter" id="editor-filter"
class="hidden block w-full h-12 rounded-lg border border-gray-300 overflow-hidden" class="hidden sm:col-span-2 block w-full h-12 rounded-lg border border-gray-300 overflow-hidden"
></div> ></div>
<p> <p class="sm:col-span-2">
With filter we specify what targets the check will run on. The must be a With filter we specify what targets the check will run on. The must be a
javascript expression that returns a boolean. javascript expression that returns a boolean.
</p> </p>
<label for="script">Script</label> <label for="script">Script</label>
<textarea required id="script" name="script" class="h-96"> <textarea required id="script" name="script" class="sm:col-span-2 h-96">
{{ ScriptUnescapeString .ExampleScript }}</textarea {{ ScriptUnescapeString .ExampleScript }}</textarea
> >
<div <div
id="editor-script" id="editor-script"
class="hidden block w-full h-96 rounded-lg border border-gray-300 overflow-hidden" class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
></div> ></div>
<p> <p class="sm:col-span-2">
Script is what determines the status of a service. You can read more Script is what determines the status of a service. You can read more
about it on about it on
<a target="_blank" href="https://k6.io/docs/using-k6/http-requests/" <a target="_blank" href="https://k6.io/docs/using-k6/http-requests/"
@ -68,7 +68,13 @@
<script src="/static/monaco/vs/loader.js"></script> <script src="/static/monaco/vs/loader.js"></script>
<script> <script>
const items = [ const items = [
{ name: "filter", language: "javascript" }, {
name: "filter",
language: "javascript",
options: {
quickSuggestions: false,
},
},
{ name: "script", language: "javascript" }, { name: "script", language: "javascript" },
]; ];
@ -80,7 +86,7 @@
} }
window.editors = {}; window.editors = {};
for (const { name, language } of items) { for (const { name, language, options = {} } of items) {
const textarea = document.getElementById(name); const textarea = document.getElementById(name);
const editor = document.getElementById("editor-" + name); const editor = document.getElementById("editor-" + name);
@ -96,6 +102,8 @@
codeLens: false, codeLens: false,
contextmenu: false, contextmenu: false,
scrollBeyondLastLine: false, scrollBeyondLastLine: false,
wordWrap: "on",
...options,
}); });
const resizeObserver = new ResizeObserver((entries) => { const resizeObserver = new ResizeObserver((entries) => {

View file

@ -29,26 +29,26 @@
<code>@yearly</code>. <code>@yearly</code>.
</p> </p>
<label for="filter">Filter</label> <label for="filter">Filter</label>
<textarea required id="filter" name="filter" class="h-12"> <textarea required id="filter" name="filter" class="sm:col-span-2 h-12">
{{ ScriptUnescapeString .Check.Filter }}</textarea {{ ScriptUnescapeString .Check.Filter }}</textarea
> >
<div <div
id="editor-filter" id="editor-filter"
class="hidden block w-full h-12 rounded-lg border border-gray-300 overflow-hidden" class="hidden sm:col-span-2 block w-full h-12 rounded-lg border border-gray-300 overflow-hidden"
></div> ></div>
<p> <p class="sm:col-span-2">
With filter we specify what targets the check will run on. The must be a With filter we specify what targets the check will run on. The must be a
javascript expression that returns a boolean. javascript expression that returns a boolean.
</p> </p>
<label for="script">Script</label> <label for="script">Script</label>
<textarea required id="script" name="script" class="h-96"> <textarea required id="script" name="script" class="sm:col-span-2 h-96">
{{ ScriptUnescapeString .Check.Script }}</textarea {{ ScriptUnescapeString .Check.Script }}</textarea
> >
<div <div
id="editor-script" id="editor-script"
class="block w-full h-96 rounded-lg border border-gray-300 overflow-hidden hidden" class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
></div> ></div>
<p> <p class="sm:col-span-2">
Script is what determines the status of a service. You can read more Script is what determines the status of a service. You can read more
about it on about it on
<a target="_blank" href="https://k6.io/docs/using-k6/http-requests/" <a target="_blank" href="https://k6.io/docs/using-k6/http-requests/"
@ -184,7 +184,13 @@
<script src="/static/monaco/vs/loader.js"></script> <script src="/static/monaco/vs/loader.js"></script>
<script> <script>
const items = [ const items = [
{ name: "filter", language: "javascript" }, {
name: "filter",
language: "javascript",
options: {
quickSuggestions: false,
},
},
{ name: "script", language: "javascript" }, { name: "script", language: "javascript" },
]; ];
@ -196,7 +202,7 @@
} }
window.editors = {}; window.editors = {};
for (const { name, language } of items) { for (const { name, language, options = {} } of items) {
const textarea = document.getElementById(name); const textarea = document.getElementById(name);
const editor = document.getElementById("editor-" + name); const editor = document.getElementById("editor-" + name);
@ -212,6 +218,8 @@
codeLens: false, codeLens: false,
contextmenu: false, contextmenu: false,
scrollBeyondLastLine: false, scrollBeyondLastLine: false,
wordWrap: "on",
...options,
}); });
const resizeObserver = new ResizeObserver((entries) => { const resizeObserver = new ResizeObserver((entries) => {

View file

@ -28,14 +28,19 @@
homepage. homepage.
</p> </p>
<label for="metadata">Metadata</label> <label for="metadata">Metadata</label>
<textarea required id="metadata" name="metadata" class="h-96"> <textarea
required
id="metadata"
name="metadata"
class="sm:col-span-2 h-96"
>
{{ ScriptUnescapeString .Example }}</textarea {{ ScriptUnescapeString .Example }}</textarea
> >
<div <div
id="editor-metadata" id="editor-metadata"
class="hidden block w-full h-96 rounded-lg border border-gray-300 overflow-hidden" class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
></div> ></div>
<p> <p class="sm:col-span-2">
Metadata is a YAML object that contains the configuration for the Metadata is a YAML object that contains the configuration for the
target. This configuration can be then used for <code>Checks</code> to target. This configuration can be then used for <code>Checks</code> to
filter the targets to act on as well as by using filter the targets to act on as well as by using
@ -58,7 +63,7 @@
} }
window.editors = {}; window.editors = {};
for (const { name, language } of items) { for (const { name, language, options = {} } of items) {
const textarea = document.getElementById(name); const textarea = document.getElementById(name);
const editor = document.getElementById("editor-" + name); const editor = document.getElementById("editor-" + name);
@ -74,6 +79,7 @@
codeLens: false, codeLens: false,
contextmenu: false, contextmenu: false,
scrollBeyondLastLine: false, scrollBeyondLastLine: false,
...options,
}); });
const resizeObserver = new ResizeObserver((entries) => { const resizeObserver = new ResizeObserver((entries) => {

View file

@ -35,14 +35,19 @@
homepage. homepage.
</p> </p>
<label for="metadata">Metadata</label> <label for="metadata">Metadata</label>
<textarea required id="metadata" name="metadata" class="h-96"> <textarea
required
id="metadata"
name="metadata"
class="sm:col-span-2 h-96"
>
{{ ScriptUnescapeString .Target.Metadata }}</textarea {{ ScriptUnescapeString .Target.Metadata }}</textarea
> >
<div <div
id="editor-metadata" id="editor-metadata"
class="hidden block w-full h-96 rounded-lg border border-gray-300 overflow-hidden" class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
></div> ></div>
<p> <p class="sm:col-span-2">
Metadata is a YAML object that contains the configuration for the Metadata is a YAML object that contains the configuration for the
target. This configuration can be then used for <code>Targets</code> to target. This configuration can be then used for <code>Targets</code> to
filter the targets to act on as well as by using filter the targets to act on as well as by using

View file

@ -10,14 +10,14 @@
/> />
<p>Name of the trigger can be anything.</p> <p>Name of the trigger can be anything.</p>
<label for="script">Script</label> <label for="script">Script</label>
<textarea required id="script" name="script" class="h-96"> <textarea required id="script" name="script" class="sm:col-span-2 h-96">
{{ ScriptUnescapeString .Example }}</textarea {{ ScriptUnescapeString .Example }}</textarea
> >
<div <div
id="editor-script" id="editor-script"
class="hidden block w-full h-96 rounded-lg border border-gray-300 overflow-hidden" class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
></div> ></div>
<p> <p class="sm:col-span-2">
The trigger script executes for every matching <code>target</code>'s The trigger script executes for every matching <code>target</code>'s
execution of <code>trigger</code>. The outcome of that execution of <code>trigger</code>. The outcome of that
<code>trigger</code> is passed to the script as a <code>trigger</code> is passed to the script as a
@ -40,7 +40,7 @@
} }
window.editors = {}; window.editors = {};
for (const { name, language } of items) { for (const { name, language, options = {} } of items) {
const textarea = document.getElementById(name); const textarea = document.getElementById(name);
const editor = document.getElementById("editor-" + name); const editor = document.getElementById("editor-" + name);
@ -56,6 +56,7 @@
codeLens: false, codeLens: false,
contextmenu: false, contextmenu: false,
scrollBeyondLastLine: false, scrollBeyondLastLine: false,
...options,
}); });
const resizeObserver = new ResizeObserver((entries) => { const resizeObserver = new ResizeObserver((entries) => {

View file

@ -3,14 +3,14 @@
<form action="/settings/triggers/{{ .Trigger.Id }}" method="post"> <form action="/settings/triggers/{{ .Trigger.Id }}" method="post">
<h2>Configuration</h2> <h2>Configuration</h2>
<label for="script">Script</label> <label for="script">Script</label>
<textarea required id="script" name="script" class="h-96"> <textarea required id="script" name="script" class="sm:col-span-2 h-96">
{{ ScriptUnescapeString .Trigger.Script }}</textarea {{ ScriptUnescapeString .Trigger.Script }}</textarea
> >
<div <div
id="editor-script" id="editor-script"
class="block w-full h-96 rounded-lg border border-gray-300 overflow-hidden hidden" class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
></div> ></div>
<p> <p class="sm:col-span-2">
The trigger script executes for every matching <code>target</code>'s The trigger script executes for every matching <code>target</code>'s
execution of <code>trigger</code>. The outcome of that execution of <code>trigger</code>. The outcome of that
<code>trigger</code> is passed to the script as a <code>trigger</code> is passed to the script as a
@ -122,7 +122,7 @@
} }
window.editors = {}; window.editors = {};
for (const { name, language } of items) { for (const { name, language, options = {} } of items) {
const textarea = document.getElementById(name); const textarea = document.getElementById(name);
const editor = document.getElementById("editor-" + name); const editor = document.getElementById("editor-" + name);
@ -138,6 +138,7 @@
codeLens: false, codeLens: false,
contextmenu: false, contextmenu: false,
scrollBeyondLastLine: false, scrollBeyondLastLine: false,
...options,
}); });
const resizeObserver = new ResizeObserver((entries) => { const resizeObserver = new ResizeObserver((entries) => {