Accept readinessScoreFeedback in workItemUpdate mutation (stacked on !252884)

What does this MR do and why?

Adds readinessScoreFeedback as a writable argument on WorkItemWidgetAgentPlanInput, accepted by the workItemUpdate mutation and gated on the existing workplan_score feature flag.

This is a restacked version of !252885 (closed). The content is the same, but the branch is stacked on the GraphQL read branch instead of the model branch.

This MR also mirrors the flag-off error behaviour introduced for readiness_score in !253065 (merged): passing readinessScoreFeedback while workplan_score is disabled now raises an explicit callback error instead of silently dropping the param.

Why restack

The argument copies its description from the read-path field:

description: copy_field_description(::Types::WorkItems::Widgets::AgentPlanType, :readiness_score_feedback)

That field is defined in !252884 (merged). On the original !252885 (closed) branch, which targets the model branch, AgentPlanType has no readiness_score_feedback field, so copy_field_description has nothing to copy from. Stacking on !252884 (merged) gives the argument a real field to reference.

Merge order is therefore !252883 (merged), then !252884 (merged), then this MR. Retarget to master once the parents merge.

What changes

  • ee/app/graphql/types/work_items/widgets/agent_plan_input_type.rb: adds the readiness_score_feedback argument (String, required: false, experiment: { milestone: '19.4' }).
  • ee/app/services/work_items/callbacks/agent_plan.rb: adds :readiness_score_feedback to ALLOWED_PARAMS, adds a readiness_score_feedback_param? predicate gated on workplan_score, includes it in the handle_agent_plan_change guard, and assigns the attribute. When readiness_score_feedback is supplied but the flag is off, raise_error is called with a clear message (mirroring the readiness_score behaviour). A feedback-only change does not fire AGENT_PLAN_CREATE or AGENT_PLAN_UPDATE.
  • doc/api/graphql/reference/_index.md: updated GraphQL reference docs.
  • locale/gitlab.pot: adds the i18n entry for the new flag-off error message.
  • Specs: input type argument list, plus callback coverage for feedback-only params, feedback with the flag off (now expects an error), feedback alongside content, score and feedback staged for a single save, and the absence of agent-plan events for feedback-only changes.

How to set up and validate locally

The source branch is stacked on !252884 (merged), which in turn includes !252883 (merged), so checking it out gives you the model column, the read field, and the write argument together.

  1. Check out the branch and make sure migrations are up to date:

    git checkout duo/feature/617982-readiness-score-feedback-graphql-write-stacked
    bin/rails db:migrate
  2. In rails console, enable the flag for a group and get a work item global ID (the namespace is derived from the work item, so the flag is checked against its root ancestor):

    group = Group.find_by_full_path('flightjs')
    Feature.enable(:workplan_score, group)
    
    work_item = group.work_items.first # or any work item under that group
    work_item.to_global_id.to_s
  3. Open the GraphiQL explorer at /-/graphql-explorer and run the mutation with the global ID from the previous step:

    mutation {
      workItemUpdate(input: {
        id: "gid://gitlab/WorkItem/1"
        agentPlanWidget: { readinessScoreFeedback: "Add **acceptance criteria**." }
      }) {
        errors
        workItem {
          widgets {
            ... on WorkItemWidgetAgentPlan {
              content
              readinessScore
              readinessScoreFeedback
              readinessScoreFeedbackHtml
            }
          }
        }
      }
    }

    Expected: errors is empty, readinessScoreFeedback returns "Add **acceptance criteria**.", readinessScoreFeedbackHtml renders acceptance criteria inside a <strong> tag, and content is unchanged.

  4. Verify that a score and its feedback land on one record. Re-run the mutation with agentPlanWidget: { readinessScore: 80, readinessScoreFeedback: "Add acceptance criteria." } and check the record count in the console:

    WorkItems::AgentPlan.count

    Expected: both fields resolve to the new values and the count is unchanged, confirming a single save rather than a second plan row.

  5. Verify the flag gate. Run Feature.disable(:workplan_score, group) in the console and re-run the mutation with different feedback. Expected: errors contains a message mentioning workplan_score, and readinessScoreFeedback retains its previous value — the param is rejected rather than silently dropped.

  6. Verify that a feedback-only change fires no agent plan event. Tail the log while running a feedback-only mutation:

    tail -f log/development.log | grep -i agent_plan

    Expected: no work_item_agent_plan_create or work_item_agent_plan_update event.

  7. Verify the length validation by sending feedback longer than WorkItems::AgentPlan::CONTENT_LENGTH_MAX. Expected: the mutation returns a validation message in errors instead of succeeding.

  8. Run the specs and the GraphQL docs check:

    bin/rspec ee/spec/graphql/types/work_items/widgets/agent_plan_input_type_spec.rb \
              ee/spec/services/work_items/callbacks/agent_plan_spec.rb
    bundle exec rake gitlab:graphql:check_docs

One gotcha when poking at this by hand: readiness_score_feedback is externally stored, so it does not roll back with a transaction and a stale in-memory record can mislead you. Call reload on the plan before asserting anything. This is also why the spec switched its agent_plan from let_it_be to let!.

References

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 Duo Developer

Merge request reports

Loading
Loading