mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-21 23:33:34 +00:00
feat: check history and ui fixes
This commit is contained in:
parent
813c76fc3e
commit
da53c04659
15 changed files with 215 additions and 97 deletions
|
@ -143,6 +143,9 @@ type TargetHistory struct {
|
|||
CreatedAt *Time `db:"created_at"`
|
||||
|
||||
TargetId string `db:"target_id"`
|
||||
WorkerGroupId string `db:"worker_group_id"`
|
||||
CheckId string `db:"check_id"`
|
||||
|
||||
Status TargetStatus `db:"status"`
|
||||
Note string `db:"note"`
|
||||
}
|
||||
|
|
|
@ -93,14 +93,18 @@ END;
|
|||
|
||||
CREATE TABLE target_histories (
|
||||
target_id TEXT NOT NULL,
|
||||
worker_group_id TEXT NOT NULL,
|
||||
check_id TEXT NOT NULL,
|
||||
|
||||
status TEXT NOT NULL,
|
||||
note TEXT NOT NULL,
|
||||
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
|
||||
|
||||
PRIMARY KEY (target_id, created_at),
|
||||
CONSTRAINT fk_target_histories_target FOREIGN KEY (target_id) REFERENCES targets(id) ON DELETE CASCADE
|
||||
PRIMARY KEY (target_id, worker_group_id, check_id, created_at),
|
||||
CONSTRAINT fk_target_histories_target FOREIGN KEY (target_id) REFERENCES targets(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_target_histories_worker_group FOREIGN KEY (worker_group_id) REFERENCES worker_groups(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_target_histories_check FOREIGN KEY (check_id) REFERENCES checks(id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
-- +migrate Down
|
||||
|
|
|
@ -20,6 +20,8 @@ func (a *Activities) AddTargetHistory(ctx context.Context, param temporal.Activi
|
|||
|
||||
err := services.AddHistoryForTarget(ctx, a.db, &models.TargetHistory{
|
||||
TargetId: param.Target.Id,
|
||||
WorkerGroupId: param.WorkerGroupId,
|
||||
CheckId: param.CheckId,
|
||||
Status: status,
|
||||
Note: param.Note,
|
||||
})
|
||||
|
|
|
@ -71,7 +71,8 @@ func (h *BaseHandler) SettingsChecksGET(c echo.Context) error {
|
|||
for i, check := range checks {
|
||||
state, err := services.GetCheckState(context.Background(), h.temporal, check.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
h.logger.Error("Failed to get check state", "error", err)
|
||||
state = models.CheckStateUnknown
|
||||
}
|
||||
checksWithState[i] = &CheckWithWorkerGroupsAndState{
|
||||
CheckWithWorkerGroups: check,
|
||||
|
|
|
@ -267,7 +267,7 @@ func CreateOrUpdateCheckSchedule(
|
|||
ID: getScheduleId(check.Id),
|
||||
Workflow: internaltemporal.WorkflowCheckName,
|
||||
Args: args,
|
||||
TaskQueue: "default",
|
||||
TaskQueue: internaltemporal.TEMPORAL_SERVER_QUEUE,
|
||||
RetryPolicy: &temporal.RetryPolicy{
|
||||
MaximumAttempts: 3,
|
||||
},
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mentos1386/zdravko/internal/temporal"
|
||||
"go.temporal.io/api/enums/v1"
|
||||
"go.temporal.io/api/workflowservice/v1"
|
||||
"go.temporal.io/sdk/client"
|
||||
)
|
||||
|
@ -13,12 +15,16 @@ type CheckHistory struct {
|
|||
CheckId string
|
||||
Status string
|
||||
Duration time.Duration
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
WorkerGroupName string
|
||||
Note string
|
||||
}
|
||||
|
||||
func GetLastNCheckHistory(ctx context.Context, temporal client.Client, n int32) ([]*CheckHistory, error) {
|
||||
func GetLastNCheckHistory(ctx context.Context, t client.Client, n int32) ([]*CheckHistory, error) {
|
||||
var checkHistory []*CheckHistory
|
||||
|
||||
response, err := temporal.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
||||
response, err := t.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
||||
PageSize: n,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -29,21 +35,37 @@ func GetLastNCheckHistory(ctx context.Context, temporal client.Client, n int32)
|
|||
|
||||
for _, execution := range executions {
|
||||
scheduleId := string(execution.GetSearchAttributes().GetIndexedFields()["TemporalScheduledById"].Data)
|
||||
checkId := scheduleId[len("check-"):]
|
||||
|
||||
// Remove the quotes around the checkId and the prefix.
|
||||
checkId := scheduleId[len("\"check-") : len(scheduleId)-1]
|
||||
|
||||
var result temporal.WorkflowCheckResult
|
||||
if execution.Status != enums.WORKFLOW_EXECUTION_STATUS_RUNNING {
|
||||
workflowRun := t.GetWorkflow(ctx, execution.GetExecution().GetWorkflowId(), execution.GetExecution().GetRunId())
|
||||
err := workflowRun.Get(ctx, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
checkHistory = append(checkHistory, &CheckHistory{
|
||||
CheckId: checkId,
|
||||
Duration: execution.CloseTime.AsTime().Sub(execution.StartTime.AsTime()),
|
||||
StartTime: execution.StartTime.AsTime(),
|
||||
EndTime: execution.CloseTime.AsTime(),
|
||||
Status: execution.Status.String(),
|
||||
WorkerGroupName: execution.GetTaskQueue(),
|
||||
Note: result.Note,
|
||||
})
|
||||
}
|
||||
|
||||
return checkHistory, nil
|
||||
}
|
||||
|
||||
func GetCheckHistoryForCheck(ctx context.Context, temporal client.Client, checkId string) ([]*CheckHistory, error) {
|
||||
func GetCheckHistoryForCheck(ctx context.Context, t client.Client, checkId string) ([]*CheckHistory, error) {
|
||||
var checkHistory []*CheckHistory
|
||||
|
||||
response, err := temporal.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
||||
response, err := t.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
||||
PageSize: 10,
|
||||
Query: fmt.Sprintf(`TemporalScheduledById = "%s"`, getScheduleId(checkId)),
|
||||
})
|
||||
|
@ -55,11 +77,27 @@ func GetCheckHistoryForCheck(ctx context.Context, temporal client.Client, checkI
|
|||
|
||||
for _, execution := range executions {
|
||||
scheduleId := string(execution.GetSearchAttributes().GetIndexedFields()["TemporalScheduledById"].Data)
|
||||
checkId := scheduleId[len("check-"):]
|
||||
|
||||
// Remove the quotes around the checkId and the prefix.
|
||||
checkId := scheduleId[len("\"check-") : len(scheduleId)-1]
|
||||
|
||||
var result temporal.WorkflowCheckResult
|
||||
if execution.Status != enums.WORKFLOW_EXECUTION_STATUS_RUNNING {
|
||||
workflowRun := t.GetWorkflow(ctx, execution.GetExecution().GetWorkflowId(), execution.GetExecution().GetRunId())
|
||||
err := workflowRun.Get(ctx, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
checkHistory = append(checkHistory, &CheckHistory{
|
||||
CheckId: checkId,
|
||||
Duration: execution.CloseTime.AsTime().Sub(execution.StartTime.AsTime()),
|
||||
StartTime: execution.StartTime.AsTime(),
|
||||
EndTime: execution.CloseTime.AsTime(),
|
||||
Status: execution.Status.String(),
|
||||
WorkerGroupName: execution.GetTaskQueue(),
|
||||
Note: result.Note,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,15 @@ package services
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mentos1386/zdravko/database/models"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/mentos1386/zdravko/database/models"
|
||||
)
|
||||
|
||||
type TargetHistory struct {
|
||||
*models.TargetHistory
|
||||
TargetName string `db:"target_name"`
|
||||
WorkerGroupName string `db:"worker_group_name"`
|
||||
CheckName string `db:"check_name"`
|
||||
}
|
||||
|
||||
func GetLastNTargetHistory(ctx context.Context, db *sqlx.DB, n int) ([]*TargetHistory, error) {
|
||||
|
@ -17,9 +19,13 @@ func GetLastNTargetHistory(ctx context.Context, db *sqlx.DB, n int) ([]*TargetHi
|
|||
err := db.SelectContext(ctx, &targetHistory, `
|
||||
SELECT
|
||||
th.*,
|
||||
t.name AS target_name
|
||||
t.name AS target_name,
|
||||
wg.name AS worker_group_name,
|
||||
c.name AS check_name
|
||||
FROM target_histories th
|
||||
LEFT JOIN targets t ON th.target_id = t.id
|
||||
LEFT JOIN worker_groups wg ON th.worker_group_id = wg.id
|
||||
LEFT JOIN checks c ON th.check_id = c.id
|
||||
WHERE th.target_id = $1
|
||||
ORDER BY th.created_at DESC
|
||||
LIMIT $1
|
||||
|
@ -32,9 +38,13 @@ func GetTargetHistoryForTarget(ctx context.Context, db *sqlx.DB, targetId string
|
|||
err := db.SelectContext(ctx, &targetHistory, `
|
||||
SELECT
|
||||
th.*,
|
||||
t.name AS target_name
|
||||
t.name AS target_name,
|
||||
wg.name AS worker_group_name,
|
||||
c.name AS check_name
|
||||
FROM target_histories th
|
||||
LEFT JOIN targets t ON th.target_id = t.id
|
||||
LEFT JOIN worker_groups wg ON th.worker_group_id = wg.id
|
||||
LEFT JOIN checks c ON th.check_id = c.id
|
||||
WHERE th.target_id = $1
|
||||
ORDER BY th.created_at DESC
|
||||
`, targetId)
|
||||
|
@ -46,10 +56,14 @@ func AddHistoryForTarget(ctx context.Context, db *sqlx.DB, history *models.Targe
|
|||
`
|
||||
INSERT INTO target_histories (
|
||||
target_id,
|
||||
worker_group_id,
|
||||
check_id,
|
||||
status,
|
||||
note
|
||||
) VALUES (
|
||||
:target_id,
|
||||
:worker_group_id,
|
||||
:check_id,
|
||||
:status,
|
||||
:note
|
||||
)`,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package workflows
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
|
@ -9,7 +8,7 @@ import (
|
|||
"go.temporal.io/sdk/workflow"
|
||||
)
|
||||
|
||||
func (w *Workflows) CheckWorkflowDefinition(ctx workflow.Context, param temporal.WorkflowCheckParam) error {
|
||||
func (w *Workflows) CheckWorkflowDefinition(ctx workflow.Context, param temporal.WorkflowCheckParam) (*temporal.WorkflowCheckResult, error) {
|
||||
workerGroupIds := param.WorkerGroupIds
|
||||
sort.Strings(workerGroupIds)
|
||||
|
||||
|
@ -25,7 +24,7 @@ func (w *Workflows) CheckWorkflowDefinition(ctx workflow.Context, param temporal
|
|||
},
|
||||
).Get(ctx, &targetsFilterResult)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, target := range targetsFilterResult.Targets {
|
||||
|
@ -43,7 +42,7 @@ func (w *Workflows) CheckWorkflowDefinition(ctx workflow.Context, param temporal
|
|||
},
|
||||
).Get(ctx, &checkResult)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status := temporal.AddTargetHistoryStatusFailure
|
||||
|
@ -60,17 +59,19 @@ func (w *Workflows) CheckWorkflowDefinition(ctx workflow.Context, param temporal
|
|||
temporal.ActivityAddTargetHistoryName,
|
||||
&temporal.ActivityAddTargetHistoryParam{
|
||||
Target: target,
|
||||
WorkerGroupId: workerGroupId,
|
||||
CheckId: param.CheckId,
|
||||
Status: status,
|
||||
Note: checkResult.Note,
|
||||
},
|
||||
).Get(ctx, &addTargetHistoryResult)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slog.Info("Check %s status: %s", param.CheckId, status)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return &temporal.WorkflowCheckResult{
|
||||
Note: "Check workflow completed",
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ const (
|
|||
|
||||
type ActivityAddTargetHistoryParam struct {
|
||||
Target *Target
|
||||
WorkerGroupId string
|
||||
CheckId string
|
||||
Status AddTargetHistoryStatus
|
||||
Note string
|
||||
}
|
||||
|
|
|
@ -7,4 +7,8 @@ type WorkflowCheckParam struct {
|
|||
WorkerGroupIds []string
|
||||
}
|
||||
|
||||
type WorkflowCheckResult struct {
|
||||
Note string
|
||||
}
|
||||
|
||||
const WorkflowCheckName = "CHECK_WORKFLOW"
|
||||
|
|
|
@ -991,6 +991,11 @@ video {
|
|||
background-color: rgb(253 186 116 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-purple-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(243 232 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-red-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
|
||||
|
@ -1225,6 +1230,11 @@ video {
|
|||
color: rgb(22 101 52 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-purple-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(107 33 168 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-red-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(220 38 38 / var(--tw-text-opacity));
|
||||
|
|
|
@ -118,7 +118,8 @@
|
|||
<th>Check ID</th>
|
||||
<th>Status</th>
|
||||
<th>Worker Group</th>
|
||||
<th>Created At</th>
|
||||
<th>Started At</th>
|
||||
<th>Ended At</th>
|
||||
<th>Duration</th>
|
||||
<th>Note</th>
|
||||
</tr>
|
||||
|
@ -130,36 +131,35 @@
|
|||
<td>{{ .CheckId }}</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ if eq .Status "SUCCESS" }}
|
||||
bg-green-100 text-green-800
|
||||
{{ else }}
|
||||
bg-red-100 text-red-800
|
||||
{{ end }}"
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||
>
|
||||
{{ .Status }}
|
||||
{{ .Status }}...
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||
>
|
||||
{ .WorkerGroupName }
|
||||
{{ .WorkerGroupName }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }</td>
|
||||
<td>/</td>
|
||||
<td class="whitespace-normal">/</td>
|
||||
<td>{{ .StartTime.Format "2006-01-02 15:04:05" }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="whitespace-normal"></td>
|
||||
</tr>
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td>{{ .CheckId }}</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ if eq .Status "SUCCESS" }}
|
||||
bg-green-100 text-green-800
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
||||
{{ if eq .Status "Completed" }}
|
||||
bg-purple-100 text-purple-800
|
||||
{{ else }}
|
||||
bg-red-100 text-red-800
|
||||
{{ end }}"
|
||||
{{ end }}
|
||||
"
|
||||
>
|
||||
{{ .Status }}
|
||||
</span>
|
||||
|
@ -168,12 +168,13 @@
|
|||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||
>
|
||||
{ .WorkerGroupName }
|
||||
{{ .WorkerGroupName }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }</td>
|
||||
<td>{{ .Duration }}</td>
|
||||
<td class="whitespace-normal">{ .Note }</td>
|
||||
<td>{{ .StartTime.Format "2006-01-02 15:04:05" }}</td>
|
||||
<td>{{ .EndTime.Format "2006-01-02 15:04:05" }}</td>
|
||||
<td>{{ DurationRoundMillisecond .Duration }}</td>
|
||||
<td class="whitespace-normal">{{ .Note }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
|
|
@ -42,53 +42,77 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<section class="mt-4">
|
||||
<section>
|
||||
<table>
|
||||
<caption>
|
||||
Execution History
|
||||
<p>Last 10 executions for all checks and worker groups.</p>
|
||||
History
|
||||
<p>Last 10 executions.</p>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Check</th>
|
||||
<th>Worker Group</th>
|
||||
<th>Check ID</th>
|
||||
<th>Status</th>
|
||||
<th>Executed At</th>
|
||||
<th>Worker Group</th>
|
||||
<th>Started At</th>
|
||||
<th>Ended At</th>
|
||||
<th>Duration</th>
|
||||
<th>Note</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .History }}
|
||||
{{ if eq .Status "Running" }}
|
||||
<tr>
|
||||
<th>
|
||||
<a
|
||||
class="underline hover:text-blue-600"
|
||||
href="/settings/checks/{{ .CheckId }}"
|
||||
>{{ .CheckId }}</a
|
||||
>
|
||||
</th>
|
||||
<td>{{ .CheckId }}</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||
>
|
||||
{ .WorkerGroupName }
|
||||
{{ .Status }}...
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ if eq .Status "SUCCESS" }}
|
||||
bg-green-100 text-green-800
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||
>
|
||||
{{ .WorkerGroupName }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ .StartTime.Format "2006-01-02 15:04:05" }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="whitespace-normal"></td>
|
||||
</tr>
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td>{{ .CheckId }}</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
||||
{{ if eq .Status "Completed" }}
|
||||
bg-purple-100 text-purple-800
|
||||
{{ else }}
|
||||
bg-red-100 text-red-800
|
||||
{{ end }}"
|
||||
{{ end }}
|
||||
"
|
||||
>
|
||||
{{ .Status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }</td>
|
||||
<td class="whitespace-normal">{ .Note }</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||
>
|
||||
{{ .WorkerGroupName }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ .StartTime.Format "2006-01-02 15:04:05" }}</td>
|
||||
<td>{{ .EndTime.Format "2006-01-02 15:04:05" }}</td>
|
||||
<td>{{ DurationRoundMillisecond .Duration }}</td>
|
||||
<td class="whitespace-normal">{{ .Note }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
|
|
@ -116,6 +116,7 @@
|
|||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Worker Group</th>
|
||||
<th>Check</th>
|
||||
<th>Created At</th>
|
||||
<th>Duration</th>
|
||||
<th>Note</th>
|
||||
|
@ -142,6 +143,13 @@
|
|||
{{ .WorkerGroupName }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||
>
|
||||
{{ .CheckName }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }}
|
||||
</td>
|
||||
|
|
|
@ -26,6 +26,12 @@ func load(files ...string) *template.Template {
|
|||
|
||||
t := template.New("default").Funcs(
|
||||
template.FuncMap{
|
||||
"DurationRoundSecond": func(d time.Duration) time.Duration {
|
||||
return d.Round(time.Second)
|
||||
},
|
||||
"DurationRoundMillisecond": func(d time.Duration) time.Duration {
|
||||
return d.Round(time.Millisecond)
|
||||
},
|
||||
"StringsJoin": strings.Join,
|
||||
"Now": time.Now,
|
||||
"ScriptUnescapeString": script.UnescapeString,
|
||||
|
|
Loading…
Reference in a new issue