Accept trigger goals when enabling a catalog item

What does this MR do and why?

This MR adds a triggerGoals argument to two mutations: aiCatalogItemConsumerCreate and aiCatalogItemConsumerBulkCreate.

A goal tells a Duo Agent Platform flow what to do. A flow trigger stores its goals in the goals column, keyed by event type name. Both mutations create a flow trigger when a user enables a catalog item. They could set triggerTypes and triggerFilter, but not a goal. The two trigger creation paths did not match. This MR closes that gap.

triggerGoals follows the route that triggerFilter already takes. The bulk path carries it through EnqueueBulkCreateService, BulkCreateWorker and BulkCreateService. CreateService puts it on the new trigger as goals.

The argument type is GraphQL::Types::JSON. This matches the goals argument that !258739 (merged) added. The goals payload keeps one shape across all three mutations. The ai_flow_trigger_goals JSON schema stays the single contract. A typed input object would repeat the event type keys and could drift from that schema.

Decisions that need a close look:

  1. CreateService#flow_triggers_not_allowed? now also checks trigger_goals. On a group level enable, the key was never deleted. It reached ItemConsumer.new and raised ActiveModel::UnknownAttributeError. The user now gets the existing "Triggers can't be added" message.

  2. foundational_flow_with_user_triggers? does not check goals on purpose. A foundational flow can opt in later with accepts_trigger_goal: true. A check here would refuse an opted-in flow. The model validation goals_supported_by_target already refuses a goal on a flow that does not accept one.

  3. The new guard raised the cyclomatic complexity of EnqueueBulkCreateService#execute above the limit. I did not disable the cop. I moved the three "not supported for this item type" checks into unsupported_trigger_argument_message. This touches two existing lines.

  4. prepare_trigger_params now deletes trigger_types, trigger_filter and trigger_goals before its early returns. The guard in point 1 tests the params for truth, so it cannot see an explicit null. GraphQL sends one as a key that holds nil. The key then survived and reached ItemConsumer.new, which has no such attribute, so the user got a 500. A null goal now means "no goal", which is what the trigger form will send for an empty field.

    trigger_types was deleted inside the custom flow branch, which three early returns can skip: a group level enable, a foundational flow, and a custom flow that sends no types. That fault pre-dates this MR. @Imjaydip found it in review, and it is fixed here because the same method had to change anyway. There is a spec for it beside the null goal spec.

Sidekiq compatibility: worker_params calls compact. A job that is already queued has no trigger_goals key. The worker reads nil. No worker versioning is needed.

Feature flag: the ai_flow_trigger_goals flag exists and is off by default. This MR adds no new flag check. The model validation goals_available refuses a goal while the flag is off. The user gets a clear error. This follows !258739 (merged).

This is MR 3.1 in a stack:

  • !256658 (merged) added the goals column and its validations.
  • !258739 (merged) added the goals GraphQL field and mutation arguments.
  • !257218 (merged) (open) composes the goal at dispatch time.
  • MR 4 (not started) adds the trigger form field and user documentation.

References

Screenshots or screen recordings

This MR has no UI change.

How to set up and validate locally

  1. In the Rails console, turn the flag on: Feature.enable(:ai_flow_trigger_goals)

  2. Find a custom AI Catalog flow and a project. Open GraphiQL at /-/graphql-explorer. Enable the flow for the project and send a goal. The event type key must keep its underscores, so send the variables as JSON.

    mutation($input: AiCatalogItemConsumerCreateInput!) {
      aiCatalogItemConsumerCreate(input: $input) { errors }
    }
    {
      "input": {
        "target": { "projectId": "gid://gitlab/Project/<id>" },
        "itemId": "gid://gitlab/Ai::Catalog::Item/<id>",
        "triggerTypes": ["pipeline_hooks"],
        "triggerGoals": { "pipeline_hooks": "Summarise the failing jobs." }
      }
    }
  3. In the Rails console, confirm the goal is stored: Ai::FlowTrigger.last.goals

  4. Repeat with aiCatalogItemConsumerBulkCreate and two project IDs. Confirm that each project's trigger holds the goal.

  5. Turn the flag off with Feature.disable(:ai_flow_trigger_goals). Send the same mutation. Confirm that the response holds the error "Flow trigger goals are not available". Confirm that no trigger is created.

  6. Send a goal for an event type that is not in triggerTypes. Confirm that the response names the unused event type.

  7. Enable an agent or flow for a group and send "triggerGoals": null. Confirm that the enable succeeds and that no trigger is created. Before this MR the response was a 500.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Justin Ho Tuan Duong

Merge request reports

Loading
Loading