Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 23 additions & 30 deletions apis/cmk/cmk-ui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/DetailedWorkflow"
$ref: "#/components/schemas/Workflow"
"400":
$ref: "#/components/responses/400"
"403":
Expand Down Expand Up @@ -1453,6 +1453,7 @@ components:
- DISCONNECTED
- PROCESSING
- FAILED
- UNDER_WORKFLOW
keyConfigurationName:
description: The name of the key configuration
type: string
Expand All @@ -1467,6 +1468,9 @@ components:
type: object
readOnly: true
properties:
worfklow:
$ref: "#/components/schemas/Workflow"
description: Workflow associated with the System if it's state is UNDER_WORKFLOW
errorCode:
type: string
description: The code of an error of a failed system
Expand Down Expand Up @@ -2558,35 +2562,24 @@ components:
example: example_key_configuration_name
parametersResourceType:
$ref: "#/components/schemas/WorkflowParametersResourceType"
DetailedWorkflow:
allOf:
- $ref: "#/components/schemas/Workflow"
- type: object
readOnly: true
required:
- approverGroups
- decisions
- availableTransitions
- approvalSummary
description: Detailed Workflow including system metadata
properties:
approverGroups:
description: The list of Groups responsible for approving the Workflow
type: array
items:
$ref: "#/components/schemas/Group"
decisions:
description: The list of decisions made by the approvers
type: array
items:
$ref: "#/components/schemas/WorkflowApprover"
availableTransitions:
description: The list of available transitions for the Workflow
type: array
items:
$ref: "#/components/schemas/WorkflowTransitionValue"
approvalSummary:
$ref: "#/components/schemas/WorkflowApprovalSummary"
approverGroups:
description: The list of Groups responsible for approving the Workflow (only populated in detailed responses)
type: array
items:
$ref: "#/components/schemas/Group"
decisions:
description: The list of decisions made by the approvers (only populated in detailed responses)
type: array
items:
$ref: "#/components/schemas/WorkflowApprover"
availableTransitions:
description: The list of available transitions for the Workflow (only populated in detailed responses)
type: array
items:
$ref: "#/components/schemas/WorkflowTransitionValue"
approvalSummary:
description: Summary of the approval decisions (only populated in detailed responses)
$ref: "#/components/schemas/WorkflowApprovalSummary"
WorkflowMetadata:
readOnly: true
type: object
Expand Down
454 changes: 210 additions & 244 deletions internal/api/cmkapi/cmkapi.go

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion internal/api/transform/system/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"

"github.com/openkcm/cmk/internal/api/cmkapi"
"github.com/openkcm/cmk/internal/api/transform/workflow"
"github.com/openkcm/cmk/internal/config"
"github.com/openkcm/cmk/internal/constants"
"github.com/openkcm/cmk/internal/model"
Expand All @@ -12,8 +13,26 @@ import (

var ErrFromAPI = errors.New("failed to transform system from API")

// ToAPIOpt is a functional option for customizing the ToAPI transformation.
type ToAPIOpt func(*cmkapi.System) error

// WithWorkflow sets the workflow field on the API system metadata.
func WithWorkflow(wf *model.Workflow, opts ...workflow.ToAPIOpt) ToAPIOpt {
return func(s *cmkapi.System) error {
apiWorkflow, err := workflow.ToAPI(*wf, opts...)
if s.Metadata == nil {
s.Metadata = &cmkapi.SystemMetadata{
Worfklow: apiWorkflow,
}
} else {
s.Metadata.Worfklow = apiWorkflow
}
return err
}
}

// ToAPI transforms a system model to an API system.
func ToAPI(system model.System, systemCfg *config.System) (*cmkapi.System, error) {
func ToAPI(system model.System, systemCfg *config.System, opts ...ToAPIOpt) (*cmkapi.System, error) {
err := sanitise.Sanitize(&system)
if err != nil {
return nil, err
Expand Down Expand Up @@ -58,6 +77,14 @@ func ToAPI(system model.System, systemCfg *config.System) (*cmkapi.System, error
}
}

// Apply optional transformations
for _, opt := range opts {
err := opt(apiSystem)
if err != nil {
return nil, err
}
}

return apiSystem, nil
}

Expand Down
125 changes: 63 additions & 62 deletions internal/api/transform/workflow/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,65 @@ import (

var ErrExpiryGreaterThanMaximum = errors.New("expiry exceeds maximum")

// ToAPIOpt is a functional option for customizing the ToAPI transformation.
type ToAPIOpt func(*cmkapi.Workflow) error

// WithDetailed enriches the workflow with detailed information (approvers, groups, transitions, summary).
func WithDetailed(
approvers []*model.WorkflowApprover,
approverGroups []*model.Group,
transitions []wfMechanism.Transition,
approvalSummary *wfMechanism.ApprovalSummary,
) ToAPIOpt {
return func(w *cmkapi.Workflow) error {
if approvers != nil {
decisions := make([]cmkapi.WorkflowApprover, 0, len(approvers))
for _, approver := range approvers {
apiApprover, err := ApproverToAPI(*approver)
if err != nil {
return err
}
decisions = append(decisions, apiApprover)
}
w.Decisions = &decisions
}

if approverGroups != nil {
apiApproverGroups := make([]cmkapi.Group, 0, len(approverGroups))
for _, group := range approverGroups {
apiGroup, err := groupTransform.ToAPI(*group)
if err != nil {
return err
}
apiApproverGroups = append(apiApproverGroups, *apiGroup)
}
w.ApproverGroups = &apiApproverGroups
}

if transitions != nil {
availableTransitions := make([]cmkapi.WorkflowTransitionValue, 0, len(transitions))
for _, transition := range transitions {
apiTransition := cmkapi.WorkflowTransitionValue(transition)
availableTransitions = append(availableTransitions, apiTransition)
}
w.AvailableTransitions = &availableTransitions
}

if approvalSummary != nil {
w.ApprovalSummary = &cmkapi.WorkflowApprovalSummary{
Approved: ptr.PointTo(approvalSummary.Approvals),
Rejected: ptr.PointTo(approvalSummary.Rejections),
Pending: ptr.PointTo(approvalSummary.Pending),
TargetScore: ptr.PointTo(approvalSummary.TargetScore),
}
}

return nil
}
}

// ToAPI converts a workflow model to an API workflow presentation.
func ToAPI(w model.Workflow) (*cmkapi.Workflow, error) {
func ToAPI(w model.Workflow, opts ...ToAPIOpt) (*cmkapi.Workflow, error) {
err := sanitise.Sanitize(&w)
if err != nil {
return nil, err
Expand All @@ -32,7 +89,7 @@ func ToAPI(w model.Workflow) (*cmkapi.Workflow, error) {
parametersResourceType = &resourceType
}

return &cmkapi.Workflow{
base := &cmkapi.Workflow{
Id: ptr.PointTo(w.ID),
InitiatorID: w.InitiatorID,
InitiatorName: w.InitiatorName,
Expand All @@ -50,73 +107,17 @@ func ToAPI(w model.Workflow) (*cmkapi.Workflow, error) {
UpdatedAt: ptr.PointTo(w.UpdatedAt),
}),
ExpiresAt: w.ExpiryDate,
}, nil
}

//nolint:funlen
func ToAPIDetailed(
w model.Workflow,
approvers []*model.WorkflowApprover,
approverGroups []*model.Group,
transitions []wfMechanism.Transition,
approvalSummary *wfMechanism.ApprovalSummary,
) (*cmkapi.DetailedWorkflow, error) {
base, err := ToAPI(w)
if err != nil {
return nil, err
}

detailed := &cmkapi.DetailedWorkflow{
Id: base.Id,
InitiatorID: base.InitiatorID,
InitiatorName: base.InitiatorName,
State: base.State,
ActionType: base.ActionType,
ArtifactType: base.ArtifactType,
ArtifactName: base.ArtifactName,
ArtifactID: base.ArtifactID,
Parameters: base.Parameters,
ParametersResourceName: base.ParametersResourceName,
ParametersResourceType: base.ParametersResourceType,
FailureReason: base.FailureReason,
Metadata: base.Metadata,
ExpiresAt: base.ExpiresAt,
ApprovalSummary: &cmkapi.WorkflowApprovalSummary{
Approved: ptr.PointTo(approvalSummary.Approvals),
Rejected: ptr.PointTo(approvalSummary.Rejections),
Pending: ptr.PointTo(approvalSummary.Pending),
TargetScore: ptr.PointTo(approvalSummary.TargetScore),
},
}

decisions := make([]cmkapi.WorkflowApprover, 0, len(approvers))
for _, approver := range approvers {
apiApprover, err := ApproverToAPI(*approver)
if err != nil {
return nil, err
}
decisions = append(decisions, apiApprover)
}
detailed.Decisions = decisions

apiApproverGroups := make([]cmkapi.Group, 0, len(approverGroups))
for _, group := range approverGroups {
apiGroup, err := groupTransform.ToAPI(*group)
// Apply optional transformations
for _, opt := range opts {
err := opt(base)
if err != nil {
return nil, err
}
apiApproverGroups = append(apiApproverGroups, *apiGroup)
}
detailed.ApproverGroups = apiApproverGroups

availableTransitions := make([]cmkapi.WorkflowTransitionValue, 0, len(transitions))
for _, transition := range transitions {
apiTransition := cmkapi.WorkflowTransitionValue(transition)
availableTransitions = append(availableTransitions, apiTransition)
}
detailed.AvailableTransitions = availableTransitions

return detailed, nil
return base, nil
}

// FromAPI converts an API workflow presentation to a workflow model.
Expand Down
69 changes: 68 additions & 1 deletion internal/controllers/cmk/system_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ package cmk

import (
"context"
"slices"

"github.com/openkcm/cmk/internal/api/cmkapi"
"github.com/openkcm/cmk/internal/api/transform/system"
wfWorkflow "github.com/openkcm/cmk/internal/api/transform/workflow"
"github.com/openkcm/cmk/internal/apierrors"
"github.com/openkcm/cmk/internal/constants"
"github.com/openkcm/cmk/internal/errs"
"github.com/openkcm/cmk/internal/manager"
"github.com/openkcm/cmk/internal/model"
"github.com/openkcm/cmk/internal/repo"
"github.com/openkcm/cmk/internal/workflow"
"github.com/openkcm/cmk/utils/odata"
"github.com/openkcm/cmk/utils/ptr"
)
Expand Down Expand Up @@ -92,7 +97,13 @@ func (c *APIController) GetSystemByID(ctx context.Context,
return nil, err
}

systemResponse, err := system.ToAPI(*sys, &c.config.ContextModels.System)
var systemResponse *cmkapi.System
if sys.Status == cmkapi.SystemStatusUNDERWORKFLOW {
systemResponse, err = c.handleSystemUnderWorkflow(ctx, sys)
} else {
systemResponse, err = system.ToAPI(*sys, &c.config.ContextModels.System)
}

if err != nil {
return nil, errs.Wrap(apierrors.ErrTransformSystemToAPI, err)
}
Expand Down Expand Up @@ -170,3 +181,59 @@ func (c *APIController) UnlinkSystemAction(

return cmkapi.UnlinkSystemAction204Response(struct{}{}), nil
}

func (c *APIController) handleSystemUnderWorkflow(
ctx context.Context,
sys *model.System,
) (*cmkapi.System, error) {
workflows, _, err := c.Manager.Workflow.GetWorkflows(ctx, manager.WorkflowFilter{
ArtifactType: workflow.ArtifactTypeSystem.String(),
ArtifactID: sys.ID,
})
if err != nil || len(workflows) < 1 {
return nil, errs.Wrapf(err, "error finding workflow")
}
wf := workflows[0]

approvers, _, err := c.Manager.Workflow.ListWorkflowApprovers(ctx, wf.ID, true, repo.Pagination{})
if err != nil {
return nil, err
}
approverGroups, err := c.Manager.Workflow.GetWorkflowApproverGroups(ctx, wf)
if err != nil {
return nil, err
}

user, err := c.Manager.User.GetUserInfo(ctx)
if err != nil {
return nil, err
}

isApprover := slices.ContainsFunc(approvers, func(e *model.WorkflowApprover) bool {
return e.UserID == user.Identifier
})

if user.Identifier != wf.InitiatorID && !isApprover {
return system.ToAPI(
*sys,
&c.config.ContextModels.System,
system.WithWorkflow(wf, wfWorkflow.WithDetailed(nil, approverGroups, nil, nil)),
)
}

transitions, err := c.Manager.Workflow.GetWorkflowAvailableTransitions(ctx, wf)
if err != nil {
return nil, err
}

approvalSummary, err := c.Manager.Workflow.GetWorkflowApprovalSummary(ctx, wf)
if err != nil {
return nil, err
}

return system.ToAPI(
*sys,
&c.config.ContextModels.System,
system.WithWorkflow(wf, wfWorkflow.WithDetailed(approvers, approverGroups, transitions, approvalSummary)),
)
}
Loading
Loading