Technical Breakdown: Slack workflow service desk ticket creation
This is the technical breakdown for Slack workflow service desk ticket creation (gitlab-org&10041 - closed)
See workflow in the epic
Architecture
Slack workflows is a feature that lets its users automate their tasks. Workflows are created by what Slack mentions is workflow steps.
As someone who builds an app for Slack, we can also create their own step that Slack users can add to their workflows. Users can configure this step, and our step can ask for some inputs that configuring person will take care of providing. We in turn promise a defined output from our step to the next step in the user's workflow.
How do workflows and steps fit together?
- As a person who wants to create a workflow can add our step for the service/automation that it provides.
- We define the
inputs
that we need. For creating Service desk issues, we can ask fortitle
,description
, etc. - We define an output that we return to the caller. In Service desk issue creation, it can be a link to the newly created issue.
How does a step interact with Slack?
Slack workflow steps work on the event-based model. Slack sends a few events with defined event types that we need to handle for managing the step lifecycle.
Configuration:
There are some actions that we need to take to create a new step within our Slack app. This includes adding a name to the step and configuring URLs that will receive callbacks to the events that we get subscribed to when creating a new step. The guide is self-explanatory to follow the steps to update the settings.
workflow_step_edit
Event 1: This event is sent when someone tries adding our step to their workflow. The response to this step is opening a modal that defines the fields of the inputs that we need from them to automate the task that we offer as part of this step. This event is also sent in case the user edits the step in the future after adding it once to the workflow.
We will define a handler in SlackInteractionService
.
Sample code for opening a modal to configure `title` and `description`
module Integrations
module SlackInteractions
module SlackWorkflowStepEvents
class WorkflowStepEdit
def initialize(params)
@params = params
@trigger_id = params[:trigger_id]
@team_id = params.dig(:team, :id)
end
def execute
post_modal
end
private
attr_accessor :params, :trigger_id, :team_id
def slack_installation
SlackIntegration.with_bot.find_by_team_id(team_id)
end
def post_modal
begin
response = ::Slack::API.new(slack_installation).post(
'views.open',
modal_view
)
end
end
def modal_view
{
trigger_id: trigger_id,
view: modal_payload
}
end
def modal_payload
{
"type": "workflow_step",
"blocks": [
{
"type": "input",
"block_id": "title_input",
"label": {
"type": "plain_text",
"text": _("Title")
},
"element": {
"type": "plain_text_input",
"action_id": "title",
"placeholder": {
"type": "plain_text",
"text": _("Incident title")
},
"focus_on_load": true
}
},
{
"block_id": "incident_description",
"type": "input",
"element": {
"type": "plain_text_input",
"multiline": true,
"action_id": "description",
"placeholder": {
"type": "plain_text",
"text": [
_("Write a description..."),
_("[Supports GitLab-flavored markdown, including quick actions]")
].join("\n\n")
}
},
"label": {
"type": "plain_text",
"text": _("Description")
}
}
]
}
end
end
end
end
end
view_submission
Event 2: Slack sends a view_submission
event when the user submits the modal that we opened in the above step. In this step, we should call the workflows.updateStep
method with inputs
that we need and outputs
that will respond back with. Since our existing incident declare
command also sends the event with same name, we need to check for type
inside the view
object and handle both the callers accordingly.
Sample code to handle the `view_submission` event and save the configuration data to Slack
module Integrations
module SlackInteractions
module SlackWorkflowStepEvents
class WorkflowStepSubmission
def initialize(params)
@params = params
@team_id = params.dig(:team, :id)
@values = params.dig(:view, :state, :values)
@workflow_step_edit_id = params.dig(:workflow_step, :workflow_step_edit_id)
end
def execute
post_save_step
end
private
attr_accessor :params, :team_id, :values, :workflow_step_edit_id
def slack_installation
SlackIntegration.with_bot.find_by_team_id(team_id)
end
def post_save_step
begin
response = ::Slack::API.new(slack_installation).post(
'workflows.updateStep',
update_body
)
end
end
def update_body
{
"workflow_step_edit_id": workflow_step_edit_id,
"inputs": {
"title": {
"value": values.dig(:title_input, :title, :value)
},
"description": {
"value": values.dig(:incident_description, :description, :value)
}
},
"outputs": [
{
"name": "Service desk issue link",
"type": "text",
"label": "Service desk issue link"
}
]
}
end
end
end
end
end
workflow_step_execute
Event 3: Slack sends a workflow_step_execute
event when a step is executed inside a user's workflow. It will have details about the inputs that we expect, and it is our responsibility to send back an output that we agreed on while saving the configuration in step 1.
Slack expects us to call workflows.stepCompleted
API in case the step execution was successful on our side. And Slack expects us to call workflows.stepFailed
API in case the step execution failed on our side.
Sample code for step submission logic for successful execution
module Integrations
module SlackEvents
class WorkflowStepExecution
include GitlabRoutingHelper
include Gitlab::Routing
def initialize(params)
@team_id = params[:team_id]
@inputs = params.dig(:event, :workflow_step, :inputs)
@workflow_step_execute_id = params.dig(:event, :workflow_step, :workflow_step_execute_id)
end
def execute
begin
response = ::Slack::API.new(slack_installation).post(
'workflows.stepCompleted',
payload
)
end
return ServiceResponse.success if response['ok']
end
private
attr_accessor :team_id, :inputs, :workflow_step_execute_id
def slack_installation
SlackIntegration.with_bot.find_by_team_id(team_id)
end
def payload
{
'workflow_step_execute_id': workflow_step_execute_id,
'outputs': {
'Service desk issue link': create_service_desk_issue
}
}
end
def create_service_desk_issue
result = ::Issues::CreateService.new(
container: Project.find_by_id(7),
current_user: User.support_bot,
params: {
title: inputs.dig(:title, :value),
description: inputs.dig(:description, :value),
confidential: true
},
spam_params: nil
).execute
issue = result[:issue]
issue_url(issue)
end
end
end
end
Assumptions & Limitations
- The person trying to use this Slack workflow step needs to enable the Service desk feature. Only then we can create issues using
support-bot
as user. - We can use the existing GitLab Slack App to add a workflow step
- We can capture the form inputs using Slack's native functionality
- Tickets will be created by the GitLab Support Bot (so it is categorized as a service desk issue)
- We will do this only for GitLab.com to start
- We can simply map the inputs to the description to start
- We can start by hardcoding some specific labels or use a template
- We are targeting access request tickets first
- Slack handle != GitLab handle. We can ask for the GitLab handle in the form...we might reference GitLab profile in the future.
Permissions & Security
- This should be in GitLab Ultimate gitlab-org&10041 (comment 1309629102)
- Anyone with access to the GitLab slack channel where the workflow is setup should be able to create a ticket
- For enabling the workflow feature in Slack for our Slack App, we will have to resubmit the app for review as we change the permission scopes for user and
workflow.steps:execute
is added to the list. - Users will need to reinstall the app in the workspace to be able to reflect this permission and the app step.
Frontend
No frontend work is expected in this feature. Everything is handled by Slack for modals.
Backend
- We will only show the modal to configure if the user is part of a project that uses at least GitLab Premium license.
- The sample code mentioned in the above architecture header can be a good starting point to add relevant classes.
- We hook the
view_submission
event andworkflow_step_edit
event inee/app/services/integrations/slack_interaction_service.rb
- We hook the
workflow_step_execute
event inee/app/workers/integrations/slack_event_worker.rb
. - We add specs to all the new code and existing entry points for events.
Database
No database changes are expected at this point. All save actions happen at the Slack end.
API
We do not need new API. But we do make some additional changes to the existing Slack API for handling workflow step events as mentioned in the architecture header.
Scheduled/Async jobs
We do not schedule any new jobs. We already have slack_event_worker
that will support a new handler for the workflow_step_excute
event.
Rollout
We can independently merge code in master as it won't get exposed until we make changes in the Slack app on Slack's dashboard.
These Slack side changes will be done once we have the entire code in the master, with specs and E2E tests as needed.
Feature flag
Since the code can be merged independently, we do not need any feature flags to block its usage. The code will be used once Slack app changes are published to the Slack app store.
Testing
We will update the specs for relevant classes and workers. Other than that, we do not need any testing, Slack app changes are only unit tested as testing them E2E is not possible without Slack's intervention.
Risks
No risks from a development perspective.
Expected white-box tests
Metrics
We can implement handlers for events that Slack sends when a workflow using our step is published, unpublished, deleted or the step is deleted from the workflow.
Documentation
The best place would be the GitLab for Slack app doc page with a new section for workflow steps.
Issue breakdown
Issue | Notes | Backend | Frontend |
---|---|---|---|
gitlab-org/gitlab#403003 (closed) | NA | NA |
Notes
As mentioned in the linked epic -
As we develop the feature, we should think about ways to make generic the capabilities. For example, integration with other form builders (e.g. Google Forms, TypeForm, something we build based on GitLab Pages or even native to GitLab forms), the mapping capability between Forms and GitLab issue type, could/should become reusable components.
We cannot reuse this code for Slack to another form builder as Slack APIs are specific.
Definition of done
The following are required prior to starting implementation:
-
Draft of breakdown issue was posted to #g_respond
with a request for feedback -
At least one engineer provided feedback -
Scope, timeline/dependencies, and technical limitations are fully defined -
Detailed breakdown issue was posted to #g_respond
with a last call for feedback -
All needed issues are created & linked to the appropriate epic -
All open threads are resolved or determined non-blocking
Open questions
- What fields should we start to support in the workflow step? - Ans here
- What error message to show when the user does not have a GitLab Premium subscription? - Ans here
- What error message to show when the step execution fails? - Ans here
- On successful execution what type of output should we give from the supported types? - Ans here
- Question mentioned in gitlab-org&10041 (comment 1332307995) regarding the approach to figure out where to create the Service desk issues and the check on licese.