Partial PR - need to generate SDK - Add endpoint to get the total free worker slots for a worker and the … (#857)

* Add endpoint to get the total free worker slots for a worker and the max runs

* update to use WorkerSempahoreCount instead of checking stepRunId

* modify the query for the new table and change the interface

* bump golangci-lint make changes to name of returned data

* revert the simple example

---------

Co-authored-by: Sean Reilly <sean@hatchet.run>
This commit is contained in:
Sean Reilly
2024-09-19 10:11:16 -07:00
committed by GitHub
parent e72faf75fd
commit 15c50f46b5
15 changed files with 648 additions and 174 deletions

View File

@@ -11,7 +11,7 @@ repos:
exclude: prisma/migrations/.*\.sql|sql/migrations/.*\.sql
- id: check-yaml
- repo: https://github.com/golangci/golangci-lint
rev: v1.57.1
rev: v1.61.0
hooks:
- id: golangci-lint
args: ["--config=.golangci.yml"]

View File

@@ -124,6 +124,8 @@ Job:
$ref: "./workflow.yaml#/Job"
Step:
$ref: "./workflow.yaml#/Step"
WorkflowWorkersCount:
$ref: "./workflow.yaml#/WorkflowWorkersCount"
WorkflowRun:
$ref: "./workflow_run.yaml#/WorkflowRun"
WorkflowRunShape:

View File

@@ -272,3 +272,15 @@ WorkflowMetrics:
groupKeyCount:
type: integer
description: The total number of concurrency group keys.
WorkflowWorkersCount:
type: object
properties:
freeSlotCount:
type: integer
maxSlotCount:
type: integer
workflowRunId:
type: string
required:
- count

View File

@@ -128,6 +128,8 @@ paths:
$ref: "./paths/step-run/step-run.yaml#/listStepRunEventsForWorkflowRun"
/api/v1/step-runs/{step-run}/archives:
$ref: "./paths/step-run/step-run.yaml#/listArchives"
/api/v1/tenants/{tenant}/workflows/{workflow}/worker-count:
$ref: "./paths/workflow/workflow.yaml#/workflowWorkersCount"
/api/v1/tenants/{tenant}/workflows/runs:
$ref: "./paths/workflow/workflow.yaml#/workflowRuns"
/api/v1/tenants/{tenant}/workflow-runs/replay:

View File

@@ -720,3 +720,50 @@ getMetrics:
summary: Get workflow metrics
tags:
- Workflow
workflowWorkersCount:
get:
x-resources: ["tenant", "workflow"]
description: Get a count of the workers available for workflow
operationId: workflow:get:workers-count
parameters:
- description: The tenant id
in: path
name: tenant
required: true
schema:
type: string
format: uuid
minLength: 36
maxLength: 36
- description: The workflow id
in: path
name: workflow
required: true
schema:
type: string
format: uuid
minLength: 36
maxLength: 36
responses:
"200":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/WorkflowWorkersCount"
description: Successfully retrieved the workflow worker count
"400":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: A malformed or bad request
"403":
content:
application/json:
schema:
$ref: "../../components/schemas/_index.yaml#/APIErrors"
description: Forbidden
summary: Get workflow worker count
tags:
- Workflow

View File

@@ -0,0 +1,38 @@
package workflows
import (
"errors"
"github.com/labstack/echo/v4"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/apierrors"
"github.com/hatchet-dev/hatchet/api/v1/server/oas/gen"
"github.com/hatchet-dev/hatchet/pkg/repository/prisma/db"
"github.com/hatchet-dev/hatchet/pkg/repository/prisma/dbsqlc"
"github.com/hatchet-dev/hatchet/pkg/repository/prisma/sqlchelpers"
)
func (t *WorkflowService) WorkflowGetWorkersCount(ctx echo.Context, request gen.WorkflowGetWorkersCountRequestObject) (gen.WorkflowGetWorkersCountResponseObject, error) {
tenant := ctx.Get("tenant").(*db.TenantModel)
w := ctx.Get("workflow").(*dbsqlc.GetWorkflowByIdRow)
workflow := sqlchelpers.UUIDToStr(w.Workflow.ID)
freeSlotCount, maxSlotCount, err := t.config.APIRepository.Workflow().GetWorkflowWorkerCount(tenant.ID, workflow)
if err != nil {
if errors.Is(err, db.ErrNotFound) {
return gen.WorkflowGetWorkersCount400JSONResponse(
apierrors.NewAPIErrors("workflow not found"),
), nil
}
return nil, err
}
return gen.WorkflowGetWorkersCount200JSONResponse(gen.WorkflowWorkersCount{
FreeSlotCount: &freeSlotCount,
MaxSlotCount: &maxSlotCount,
WorkflowRunId: &workflow,
}), nil
}

View File

@@ -1263,6 +1263,13 @@ type WorkflowVersionMeta struct {
WorkflowId string `json:"workflowId"`
}
// WorkflowWorkersCount defines model for WorkflowWorkersCount.
type WorkflowWorkersCount struct {
FreeSlotCount *int `json:"freeSlotCount,omitempty"`
MaxSlotCount *int `json:"maxSlotCount,omitempty"`
WorkflowRunId *string `json:"workflowRunId,omitempty"`
}
// StepRunListArchivesParams defines parameters for StepRunListArchives.
type StepRunListArchivesParams struct {
// Offset The number to skip
@@ -1673,6 +1680,9 @@ type ServerInterface interface {
// Get workflow runs
// (GET /api/v1/tenants/{tenant}/workflows/runs/metrics)
WorkflowRunGetMetrics(ctx echo.Context, tenant openapi_types.UUID, params WorkflowRunGetMetricsParams) error
// Get workflow worker count
// (GET /api/v1/tenants/{tenant}/workflows/{workflow}/worker-count)
WorkflowGetWorkersCount(ctx echo.Context, tenant openapi_types.UUID, workflow openapi_types.UUID) error
// Get current user
// (GET /api/v1/users/current)
UserGetCurrent(ctx echo.Context) error
@@ -3218,6 +3228,34 @@ func (w *ServerInterfaceWrapper) WorkflowRunGetMetrics(ctx echo.Context) error {
return err
}
// WorkflowGetWorkersCount converts echo context to params.
func (w *ServerInterfaceWrapper) WorkflowGetWorkersCount(ctx echo.Context) error {
var err error
// ------------- Path parameter "tenant" -------------
var tenant openapi_types.UUID
err = runtime.BindStyledParameterWithLocation("simple", false, "tenant", runtime.ParamLocationPath, ctx.Param("tenant"), &tenant)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tenant: %s", err))
}
// ------------- Path parameter "workflow" -------------
var workflow openapi_types.UUID
err = runtime.BindStyledParameterWithLocation("simple", false, "workflow", runtime.ParamLocationPath, ctx.Param("workflow"), &workflow)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter workflow: %s", err))
}
ctx.Set(BearerAuthScopes, []string{})
ctx.Set(CookieAuthScopes, []string{})
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.WorkflowGetWorkersCount(ctx, tenant, workflow)
return err
}
// UserGetCurrent converts echo context to params.
func (w *ServerInterfaceWrapper) UserGetCurrent(ctx echo.Context) error {
var err error
@@ -3662,6 +3700,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.POST(baseURL+"/api/v1/tenants/:tenant/workflows/cancel", wrapper.WorkflowRunCancel)
router.GET(baseURL+"/api/v1/tenants/:tenant/workflows/runs", wrapper.WorkflowRunList)
router.GET(baseURL+"/api/v1/tenants/:tenant/workflows/runs/metrics", wrapper.WorkflowRunGetMetrics)
router.GET(baseURL+"/api/v1/tenants/:tenant/workflows/:workflow/worker-count", wrapper.WorkflowGetWorkersCount)
router.GET(baseURL+"/api/v1/users/current", wrapper.UserGetCurrent)
router.GET(baseURL+"/api/v1/users/github/callback", wrapper.UserUpdateGithubOauthCallback)
router.GET(baseURL+"/api/v1/users/github/start", wrapper.UserUpdateGithubOauthStart)
@@ -5776,6 +5815,42 @@ func (response WorkflowRunGetMetrics403JSONResponse) VisitWorkflowRunGetMetricsR
return json.NewEncoder(w).Encode(response)
}
type WorkflowGetWorkersCountRequestObject struct {
Tenant openapi_types.UUID `json:"tenant"`
Workflow openapi_types.UUID `json:"workflow"`
}
type WorkflowGetWorkersCountResponseObject interface {
VisitWorkflowGetWorkersCountResponse(w http.ResponseWriter) error
}
type WorkflowGetWorkersCount200JSONResponse WorkflowWorkersCount
func (response WorkflowGetWorkersCount200JSONResponse) VisitWorkflowGetWorkersCountResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
return json.NewEncoder(w).Encode(response)
}
type WorkflowGetWorkersCount400JSONResponse APIErrors
func (response WorkflowGetWorkersCount400JSONResponse) VisitWorkflowGetWorkersCountResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(400)
return json.NewEncoder(w).Encode(response)
}
type WorkflowGetWorkersCount403JSONResponse APIErrors
func (response WorkflowGetWorkersCount403JSONResponse) VisitWorkflowGetWorkersCountResponse(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(403)
return json.NewEncoder(w).Encode(response)
}
type UserGetCurrentRequestObject struct {
}
@@ -6738,6 +6813,8 @@ type StrictServerInterface interface {
WorkflowRunGetMetrics(ctx echo.Context, request WorkflowRunGetMetricsRequestObject) (WorkflowRunGetMetricsResponseObject, error)
WorkflowGetWorkersCount(ctx echo.Context, request WorkflowGetWorkersCountRequestObject) (WorkflowGetWorkersCountResponseObject, error)
UserGetCurrent(ctx echo.Context, request UserGetCurrentRequestObject) (UserGetCurrentResponseObject, error)
UserUpdateGithubOauthCallback(ctx echo.Context, request UserUpdateGithubOauthCallbackRequestObject) (UserUpdateGithubOauthCallbackResponseObject, error)
@@ -8294,6 +8371,32 @@ func (sh *strictHandler) WorkflowRunGetMetrics(ctx echo.Context, tenant openapi_
return nil
}
// WorkflowGetWorkersCount operation middleware
func (sh *strictHandler) WorkflowGetWorkersCount(ctx echo.Context, tenant openapi_types.UUID, workflow openapi_types.UUID) error {
var request WorkflowGetWorkersCountRequestObject
request.Tenant = tenant
request.Workflow = workflow
handler := func(ctx echo.Context, request interface{}) (interface{}, error) {
return sh.ssi.WorkflowGetWorkersCount(ctx, request.(WorkflowGetWorkersCountRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "WorkflowGetWorkersCount")
}
response, err := handler(ctx, request)
if err != nil {
return err
} else if validResponse, ok := response.(WorkflowGetWorkersCountResponseObject); ok {
return validResponse.VisitWorkflowGetWorkersCountResponse(ctx.Response())
} else if response != nil {
return fmt.Errorf("Unexpected response type: %T", response)
}
return nil
}
// UserGetCurrent operation middleware
func (sh *strictHandler) UserGetCurrent(ctx echo.Context) error {
var request UserGetCurrentRequestObject
@@ -8890,176 +8993,177 @@ func (sh *strictHandler) WorkflowVersionGet(ctx echo.Context, workflow openapi_t
var swaggerSpec = []string{
"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",
"/w8AAP//Zv+ztY2oAQA=",
"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",
}
// GetSwagger returns the content of the embedded swagger specification file

View File

@@ -89,6 +89,7 @@ import {
WorkflowRunStatus,
WorkflowRunStatusList,
WorkflowVersion,
WorkflowWorkersCount,
} from './data-contracts';
import { ContentType, HttpClient, RequestParams } from './http-client';
@@ -1315,6 +1316,23 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
format: 'json',
...params,
});
/**
* @description Get a count of the workers available for workflow
*
* @tags Workflow
* @name WorkflowGetWorkersCount
* @summary Get workflow worker count
* @request GET:/api/v1/tenants/{tenant}/workflows/{workflow}/worker-count
* @secure
*/
workflowGetWorkersCount = (tenant: string, workflow: string, params: RequestParams = {}) =>
this.request<WorkflowWorkersCount, APIErrors>({
path: `/api/v1/tenants/${tenant}/workflows/${workflow}/worker-count`,
method: 'GET',
secure: true,
format: 'json',
...params,
});
/**
* @description Get all workflow runs for a tenant
*

View File

@@ -614,6 +614,12 @@ export interface Step {
parents?: string[];
}
export interface WorkflowWorkersCount {
freeSlotCount?: number;
maxSlotCount?: number;
workflowRunId?: string;
}
export interface WorkflowRun {
metadata: APIResourceMeta;
tenantId: string;

View File

@@ -1,14 +1,15 @@
#!/bin/bash
MIGRATION_NAME=$1
# check if the first argument is empty
if [ -z "$1" ]; then
echo "Usage: $0 <version>"
exit 1
if [ -z "$MIGRATION_NAME" ]; then
MIGRATION_NAME="temp"
fi
atlas migrate hash --dir "file://sql/migrations"
atlas migrate diff $1 \
atlas migrate diff $MIGRATION_NAME \
--dir "file://sql/migrations" \
--to "file://sql/schema/schema.sql" \
--dev-url "docker://postgres/15/dev?search_path=public"

View File

@@ -1260,6 +1260,13 @@ type WorkflowVersionMeta struct {
WorkflowId string `json:"workflowId"`
}
// WorkflowWorkersCount defines model for WorkflowWorkersCount.
type WorkflowWorkersCount struct {
FreeSlotCount *int `json:"freeSlotCount,omitempty"`
MaxSlotCount *int `json:"maxSlotCount,omitempty"`
WorkflowRunId *string `json:"workflowRunId,omitempty"`
}
// StepRunListArchivesParams defines parameters for StepRunListArchives.
type StepRunListArchivesParams struct {
// Offset The number to skip
@@ -1771,6 +1778,9 @@ type ClientInterface interface {
// WorkflowRunGetMetrics request
WorkflowRunGetMetrics(ctx context.Context, tenant openapi_types.UUID, params *WorkflowRunGetMetricsParams, reqEditors ...RequestEditorFn) (*http.Response, error)
// WorkflowGetWorkersCount request
WorkflowGetWorkersCount(ctx context.Context, tenant openapi_types.UUID, workflow openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error)
// UserGetCurrent request
UserGetCurrent(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
@@ -2707,6 +2717,18 @@ func (c *Client) WorkflowRunGetMetrics(ctx context.Context, tenant openapi_types
return c.Client.Do(req)
}
func (c *Client) WorkflowGetWorkersCount(ctx context.Context, tenant openapi_types.UUID, workflow openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewWorkflowGetWorkersCountRequest(c.Server, tenant, workflow)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *Client) UserGetCurrent(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewUserGetCurrentRequest(c.Server)
if err != nil {
@@ -5944,6 +5966,47 @@ func NewWorkflowRunGetMetricsRequest(server string, tenant openapi_types.UUID, p
return req, nil
}
// NewWorkflowGetWorkersCountRequest generates requests for WorkflowGetWorkersCount
func NewWorkflowGetWorkersCountRequest(server string, tenant openapi_types.UUID, workflow openapi_types.UUID) (*http.Request, error) {
var err error
var pathParam0 string
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "tenant", runtime.ParamLocationPath, tenant)
if err != nil {
return nil, err
}
var pathParam1 string
pathParam1, err = runtime.StyleParamWithLocation("simple", false, "workflow", runtime.ParamLocationPath, workflow)
if err != nil {
return nil, err
}
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/api/v1/tenants/%s/workflows/%s/worker-count", pathParam0, pathParam1)
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", queryURL.String(), nil)
if err != nil {
return nil, err
}
return req, nil
}
// NewUserGetCurrentRequest generates requests for UserGetCurrent
func NewUserGetCurrentRequest(server string) (*http.Request, error) {
var err error
@@ -7042,6 +7105,9 @@ type ClientWithResponsesInterface interface {
// WorkflowRunGetMetricsWithResponse request
WorkflowRunGetMetricsWithResponse(ctx context.Context, tenant openapi_types.UUID, params *WorkflowRunGetMetricsParams, reqEditors ...RequestEditorFn) (*WorkflowRunGetMetricsResponse, error)
// WorkflowGetWorkersCountWithResponse request
WorkflowGetWorkersCountWithResponse(ctx context.Context, tenant openapi_types.UUID, workflow openapi_types.UUID, reqEditors ...RequestEditorFn) (*WorkflowGetWorkersCountResponse, error)
// UserGetCurrentWithResponse request
UserGetCurrentWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserGetCurrentResponse, error)
@@ -8475,6 +8541,30 @@ func (r WorkflowRunGetMetricsResponse) StatusCode() int {
return 0
}
type WorkflowGetWorkersCountResponse struct {
Body []byte
HTTPResponse *http.Response
JSON200 *WorkflowWorkersCount
JSON400 *APIErrors
JSON403 *APIErrors
}
// Status returns HTTPResponse.Status
func (r WorkflowGetWorkersCountResponse) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r WorkflowGetWorkersCountResponse) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type UserGetCurrentResponse struct {
Body []byte
HTTPResponse *http.Response
@@ -9645,6 +9735,15 @@ func (c *ClientWithResponses) WorkflowRunGetMetricsWithResponse(ctx context.Cont
return ParseWorkflowRunGetMetricsResponse(rsp)
}
// WorkflowGetWorkersCountWithResponse request returning *WorkflowGetWorkersCountResponse
func (c *ClientWithResponses) WorkflowGetWorkersCountWithResponse(ctx context.Context, tenant openapi_types.UUID, workflow openapi_types.UUID, reqEditors ...RequestEditorFn) (*WorkflowGetWorkersCountResponse, error) {
rsp, err := c.WorkflowGetWorkersCount(ctx, tenant, workflow, reqEditors...)
if err != nil {
return nil, err
}
return ParseWorkflowGetWorkersCountResponse(rsp)
}
// UserGetCurrentWithResponse request returning *UserGetCurrentResponse
func (c *ClientWithResponses) UserGetCurrentWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserGetCurrentResponse, error) {
rsp, err := c.UserGetCurrent(ctx, reqEditors...)
@@ -12150,6 +12249,46 @@ func ParseWorkflowRunGetMetricsResponse(rsp *http.Response) (*WorkflowRunGetMetr
return response, nil
}
// ParseWorkflowGetWorkersCountResponse parses an HTTP response from a WorkflowGetWorkersCountWithResponse call
func ParseWorkflowGetWorkersCountResponse(rsp *http.Response) (*WorkflowGetWorkersCountResponse, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &WorkflowGetWorkersCountResponse{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest WorkflowWorkersCount
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400:
var dest APIErrors
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON400 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403:
var dest APIErrors
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON403 = &dest
}
return response, nil
}
// ParseUserGetCurrentResponse parses an HTTP response from a UserGetCurrentWithResponse call
func ParseUserGetCurrentResponse(rsp *http.Response) (*UserGetCurrentResponse, error) {
bodyBytes, err := io.ReadAll(rsp.Body)

View File

@@ -469,6 +469,40 @@ SET
WHERE "id" = @id::uuid
RETURNING *;
-- name: GetWorkflowWorkerCount :one
WITH UniqueWorkers AS (
SELECT DISTINCT w."id" AS workerId
FROM "Worker" w
JOIN "_ActionToWorker" atw ON w."id" = atw."B"
JOIN "Action" a ON atw."A" = a."id"
JOIN "Step" s ON a."actionId" = s."actionId"
JOIN "Job" j ON s."jobId" = j."id"
JOIN "WorkflowVersion" workflowVersion ON j."workflowVersionId" = workflowVersion."id"
WHERE
w."tenantId" = @tenantId::uuid
AND workflowVersion."deletedAt" IS NULL
AND w."deletedAt" IS NULL
AND w."dispatcherId" IS NOT NULL
AND w."lastHeartbeatAt" > NOW() - INTERVAL '5 seconds'
AND w."isActive" = true
AND w."isPaused" = false
AND workflowVersion."workflowId" = @workflowId::uuid
),
workers AS (
SELECT SUM("maxRuns") AS maxR
FROM "Worker"
WHERE "id" IN (SELECT workerId FROM UniqueWorkers)
),
slots AS (
SELECT COUNT(*) AS usedSlotCount
FROM "SemaphoreQueueItem" sqi
WHERE sqi."workerId" IN (SELECT workerId FROM UniqueWorkers)
)
SELECT
COALESCE(maxR, 0) AS totalSlotCount,
COALESCE(maxR, 0) - COALESCE(usedSlotCount, 0) AS freeSlotCount
FROM workers, slots;
-- name: GetWorkflowVersionCronTriggerRefs :many
SELECT
wtc.*

View File

@@ -1042,6 +1042,58 @@ func (q *Queries) GetWorkflowVersionScheduleTriggerRefs(ctx context.Context, db
return items, nil
}
const getWorkflowWorkerCount = `-- name: GetWorkflowWorkerCount :one
WITH UniqueWorkers AS (
SELECT DISTINCT w."id" AS workerId
FROM "Worker" w
JOIN "_ActionToWorker" atw ON w."id" = atw."B"
JOIN "Action" a ON atw."A" = a."id"
JOIN "Step" s ON a."actionId" = s."actionId"
JOIN "Job" j ON s."jobId" = j."id"
JOIN "WorkflowVersion" workflowVersion ON j."workflowVersionId" = workflowVersion."id"
WHERE
w."tenantId" = $1::uuid
AND workflowVersion."deletedAt" IS NULL
AND w."deletedAt" IS NULL
AND w."dispatcherId" IS NOT NULL
AND w."lastHeartbeatAt" > NOW() - INTERVAL '5 seconds'
AND w."isActive" = true
AND w."isPaused" = false
AND workflowVersion."workflowId" = $2::uuid
),
workers AS (
SELECT SUM("maxRuns") AS maxR
FROM "Worker"
WHERE "id" IN (SELECT workerId FROM UniqueWorkers)
),
slots AS (
SELECT COUNT(*) AS usedSlotCount
FROM "SemaphoreQueueItem" sqi
WHERE sqi."workerId" IN (SELECT workerId FROM UniqueWorkers)
)
SELECT
COALESCE(maxR, 0) AS totalSlotCount,
COALESCE(maxR, 0) - COALESCE(usedSlotCount, 0) AS freeSlotCount
FROM workers, slots
`
type GetWorkflowWorkerCountParams struct {
Tenantid pgtype.UUID `json:"tenantid"`
Workflowid pgtype.UUID `json:"workflowid"`
}
type GetWorkflowWorkerCountRow struct {
Totalslotcount int64 `json:"totalslotcount"`
Freeslotcount int32 `json:"freeslotcount"`
}
func (q *Queries) GetWorkflowWorkerCount(ctx context.Context, db DBTX, arg GetWorkflowWorkerCountParams) (*GetWorkflowWorkerCountRow, error) {
row := db.QueryRow(ctx, getWorkflowWorkerCount, arg.Tenantid, arg.Workflowid)
var i GetWorkflowWorkerCountRow
err := row.Scan(&i.Totalslotcount, &i.Freeslotcount)
return &i, err
}
const linkOnFailureJob = `-- name: LinkOnFailureJob :one
UPDATE "WorkflowVersion"
SET "onFailureJobId" = $1::uuid

View File

@@ -531,6 +531,22 @@ func (r *workflowEngineRepository) ListWorkflowsForEvent(ctx context.Context, te
return workflows, nil
}
func (r *workflowAPIRepository) GetWorkflowWorkerCount(tenantId, workflowId string) (int, int, error) {
params := dbsqlc.GetWorkflowWorkerCountParams{
Tenantid: sqlchelpers.UUIDFromStr(tenantId),
Workflowid: sqlchelpers.UUIDFromStr(workflowId),
}
results, err := r.queries.GetWorkflowWorkerCount(context.Background(), r.pool, params)
if err != nil {
return 0, 0, err
}
return int(results.Freeslotcount), int(results.Totalslotcount), nil
}
func (r *workflowEngineRepository) createWorkflowVersionTxs(ctx context.Context, tx pgx.Tx, tenantId, workflowId pgtype.UUID, opts *repository.CreateWorkflowVersionOpts) (string, error) {
workflowVersionId := uuid.New().String()

View File

@@ -238,6 +238,9 @@ type WorkflowAPIRepository interface {
// GetWorkflowVersionMetrics returns the metrics for a given workflow version.
GetWorkflowMetrics(tenantId, workflowId string, opts *GetWorkflowMetricsOpts) (*WorkflowMetrics, error)
// GetWorkflowWorkerCount returns the number of workers for a given workflow.
GetWorkflowWorkerCount(tenantId, workflowId string) (int, int, error)
}
type WorkflowEngineRepository interface {