mirror of https://github.com/go-gitea/gitea
Actions support workflow dispatch event (#28163)
fix #23668 My plan: * In the `actions.list` method, if workflow is selected and IsAdmin, check whether the on event contains `workflow_dispatch`. If so, display a `Run workflow` button to allow the user to manually trigger the run. * Providing a form that allows users to select target brach or tag, and these parameters can be configured in yaml * Simple form validation, `required` input cannot be empty * Add a route `/actions/run`, and an `actions.Run` method to handle * Add `WorkflowDispatchPayload` struct to pass the Webhook event payload to the runner when triggered, this payload carries the `inputs` values and other fields, doc: [workflow_dispatch payload](https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_dispatch) Other PRs * the `Workflow.WorkflowDispatchConfig()` method still return non-nil when workflow_dispatch is not defined. I submitted a PR https://gitea.com/gitea/act/pulls/85 to fix it. Still waiting for them to process. Behavior should be same with github, but may cause confusion. Here's a quick reminder. * [Doc](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch) Said: This event will `only` trigger a workflow run if the workflow file is `on the default branch`. * If the workflow yaml file only exists in a non-default branch, it cannot be triggered. (It will not even show up in the workflow list) * If the same workflow yaml file exists in each branch at the same time, the version of the default branch is used. Even if `Use workflow from` selects another branch ![image](https://github.com/go-gitea/gitea/assets/3114995/4bf596f3-426b-48e8-9b8f-0f6d18defd79) ```yaml name: Docker Image CI on: workflow_dispatch: inputs: logLevel: description: 'Log level' required: true default: 'warning' type: choice options: - info - warning - debug tags: description: 'Test scenario tags' required: false type: boolean boolean_default_true: description: 'Test scenario tags' required: true type: boolean default: true boolean_default_false: description: 'Test scenario tags' required: false type: boolean default: false environment: description: 'Environment to run tests against' type: environment required: true default: 'environment values' number_required_1: description: 'number ' type: number required: true default: '100' number_required_2: description: 'number' type: number required: true default: '100' number_required_3: description: 'number' type: number required: true default: '100' number_1: description: 'number' type: number required: false number_2: description: 'number' type: number required: false number_3: description: 'number' type: number required: false env: inputs_logLevel: ${{ inputs.logLevel }} inputs_tags: ${{ inputs.tags }} inputs_boolean_default_true: ${{ inputs.boolean_default_true }} inputs_boolean_default_false: ${{ inputs.boolean_default_false }} inputs_environment: ${{ inputs.environment }} inputs_number_1: ${{ inputs.number_1 }} inputs_number_2: ${{ inputs.number_2 }} inputs_number_3: ${{ inputs.number_3 }} inputs_number_required_1: ${{ inputs.number_required_1 }} inputs_number_required_2: ${{ inputs.number_required_2 }} inputs_number_required_3: ${{ inputs.number_required_3 }} jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: ls -la - run: env | grep inputs - run: echo ${{ inputs.logLevel }} - run: echo ${{ inputs.boolean_default_false }} ``` ![image](https://github.com/go-gitea/gitea/assets/3114995/a58a842d-a0ff-4618-bc6d-83a9596d07c8) ![image](https://github.com/go-gitea/gitea/assets/3114995/44a7cca5-7bd4-42a9-8723-91751a501c88) --------- Co-authored-by: TKaxv_7S <954067342@qq.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Denys Konovalov <kontakt@denyskon.de> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>pull/31866/head^2
parent
561b5c504f
commit
36232b69db
@ -0,0 +1,156 @@ |
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
|
||||
act_model "github.com/nektos/act/pkg/model" |
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestReadWorkflow_WorkflowDispatchConfig(t *testing.T) { |
||||
yaml := ` |
||||
name: local-action-docker-url |
||||
` |
||||
workflow, err := act_model.ReadWorkflow(strings.NewReader(yaml)) |
||||
assert.NoError(t, err, "read workflow should succeed") |
||||
workflowDispatch := workflowDispatchConfig(workflow) |
||||
assert.Nil(t, workflowDispatch) |
||||
|
||||
yaml = ` |
||||
name: local-action-docker-url |
||||
on: push |
||||
` |
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml)) |
||||
assert.NoError(t, err, "read workflow should succeed") |
||||
workflowDispatch = workflowDispatchConfig(workflow) |
||||
assert.Nil(t, workflowDispatch) |
||||
|
||||
yaml = ` |
||||
name: local-action-docker-url |
||||
on: workflow_dispatch |
||||
` |
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml)) |
||||
assert.NoError(t, err, "read workflow should succeed") |
||||
workflowDispatch = workflowDispatchConfig(workflow) |
||||
assert.NotNil(t, workflowDispatch) |
||||
assert.Nil(t, workflowDispatch.Inputs) |
||||
|
||||
yaml = ` |
||||
name: local-action-docker-url |
||||
on: [push, pull_request] |
||||
` |
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml)) |
||||
assert.NoError(t, err, "read workflow should succeed") |
||||
workflowDispatch = workflowDispatchConfig(workflow) |
||||
assert.Nil(t, workflowDispatch) |
||||
|
||||
yaml = ` |
||||
name: local-action-docker-url |
||||
on: |
||||
push: |
||||
pull_request: |
||||
` |
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml)) |
||||
assert.NoError(t, err, "read workflow should succeed") |
||||
workflowDispatch = workflowDispatchConfig(workflow) |
||||
assert.Nil(t, workflowDispatch) |
||||
|
||||
yaml = ` |
||||
name: local-action-docker-url |
||||
on: [push, workflow_dispatch] |
||||
` |
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml)) |
||||
assert.NoError(t, err, "read workflow should succeed") |
||||
workflowDispatch = workflowDispatchConfig(workflow) |
||||
assert.NotNil(t, workflowDispatch) |
||||
assert.Nil(t, workflowDispatch.Inputs) |
||||
|
||||
yaml = ` |
||||
name: local-action-docker-url |
||||
on: |
||||
- push |
||||
- workflow_dispatch |
||||
` |
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml)) |
||||
assert.NoError(t, err, "read workflow should succeed") |
||||
workflowDispatch = workflowDispatchConfig(workflow) |
||||
assert.NotNil(t, workflowDispatch) |
||||
assert.Nil(t, workflowDispatch.Inputs) |
||||
|
||||
yaml = ` |
||||
name: local-action-docker-url |
||||
on: |
||||
push: |
||||
pull_request: |
||||
workflow_dispatch: |
||||
inputs: |
||||
` |
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml)) |
||||
assert.NoError(t, err, "read workflow should succeed") |
||||
workflowDispatch = workflowDispatchConfig(workflow) |
||||
assert.NotNil(t, workflowDispatch) |
||||
assert.Nil(t, workflowDispatch.Inputs) |
||||
|
||||
yaml = ` |
||||
name: local-action-docker-url |
||||
on: |
||||
push: |
||||
pull_request: |
||||
workflow_dispatch: |
||||
inputs: |
||||
logLevel: |
||||
description: 'Log level' |
||||
required: true |
||||
default: 'warning' |
||||
type: choice |
||||
options: |
||||
- info |
||||
- warning |
||||
- debug |
||||
boolean_default_true: |
||||
description: 'Test scenario tags' |
||||
required: true |
||||
type: boolean |
||||
default: true |
||||
boolean_default_false: |
||||
description: 'Test scenario tags' |
||||
required: true |
||||
type: boolean |
||||
default: false |
||||
` |
||||
|
||||
workflow, err = act_model.ReadWorkflow(strings.NewReader(yaml)) |
||||
assert.NoError(t, err, "read workflow should succeed") |
||||
workflowDispatch = workflowDispatchConfig(workflow) |
||||
assert.NotNil(t, workflowDispatch) |
||||
assert.Equal(t, WorkflowDispatchInput{ |
||||
Name: "logLevel", |
||||
Default: "warning", |
||||
Description: "Log level", |
||||
Options: []string{ |
||||
"info", |
||||
"warning", |
||||
"debug", |
||||
}, |
||||
Required: true, |
||||
Type: "choice", |
||||
}, workflowDispatch.Inputs[0]) |
||||
assert.Equal(t, WorkflowDispatchInput{ |
||||
Name: "boolean_default_true", |
||||
Default: "true", |
||||
Description: "Test scenario tags", |
||||
Required: true, |
||||
Type: "boolean", |
||||
}, workflowDispatch.Inputs[1]) |
||||
assert.Equal(t, WorkflowDispatchInput{ |
||||
Name: "boolean_default_false", |
||||
Default: "false", |
||||
Description: "Test scenario tags", |
||||
Required: true, |
||||
Type: "boolean", |
||||
}, workflowDispatch.Inputs[2]) |
||||
} |
@ -0,0 +1,78 @@ |
||||
<div class="ui blue info message tw-flex tw-justify-between tw-items-center"> |
||||
<span class="ui text middle">{{ctx.Locale.Tr "actions.workflow.has_workflow_dispatch"}}</span> |
||||
<button class="ui mini button show-modal" data-modal="#runWorkflowDispatchModal">{{ctx.Locale.Tr "actions.workflow.run"}}{{svg "octicon-triangle-down" 14 "dropdown icon"}}</button> |
||||
</div> |
||||
<div id="runWorkflowDispatchModal" class="ui tiny modal"> |
||||
<div class="content"> |
||||
<form id="runWorkflowDispatchForm" class="ui form" action="{{$.Link}}/run?workflow={{$.CurWorkflow}}&actor={{$.CurActor}}&status={{.Status}}" method="post"> |
||||
{{.CsrfTokenHtml}} |
||||
<div class="ui inline field required tw-flex tw-items-center"> |
||||
<span class="ui inline required field"> |
||||
<label>{{ctx.Locale.Tr "actions.workflow.from_ref"}}:</label> |
||||
</span> |
||||
<div class="ui inline field dropdown button select-branch branch-selector-dropdown ellipsis-items-nowrap"> |
||||
<input type="hidden" name="ref" value="refs/heads/{{index .Branches 0}}"> |
||||
{{svg "octicon-git-branch" 14}} |
||||
<div class="default text">{{index .Branches 0}}</div> |
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}} |
||||
<div class="menu transition"> |
||||
<div class="ui icon search input"> |
||||
<i class="icon">{{svg "octicon-filter" 16}}</i> |
||||
<input name="search" type="text" placeholder="{{ctx.Locale.Tr "repo.filter_branch_and_tag"}}..."> |
||||
</div> |
||||
<div class="branch-tag-tab"> |
||||
<a class="branch-tag-item reference column muted active" href="#" data-target="#branch-list"> |
||||
{{svg "octicon-git-branch" 16 "tw-mr-1"}} {{ctx.Locale.Tr "repo.branches"}} |
||||
</a> |
||||
<a class="branch-tag-item reference column muted" href="#" data-target="#tag-list"> |
||||
{{svg "octicon-tag" 16 "tw-mr-1"}} {{ctx.Locale.Tr "repo.tags"}} |
||||
</a> |
||||
</div> |
||||
<div class="branch-tag-divider"></div> |
||||
<div id="branch-list" class="scrolling menu reference-list-menu"> |
||||
{{range .Branches}} |
||||
<div class="item" data-value="refs/heads/{{.}}" title="{{.}}">{{.}}</div> |
||||
{{else}} |
||||
<div class="item">{{ctx.Locale.Tr "no_results_found"}}</div> |
||||
{{end}} |
||||
</div> |
||||
<div id="tag-list" class="scrolling menu reference-list-menu tw-hidden"> |
||||
{{range .Tags}} |
||||
<div class="item" data-value="refs/tags/{{.}}" title="{{.}}">{{.}}</div> |
||||
{{else}} |
||||
<div class="item">{{ctx.Locale.Tr "no_results_found"}}</div> |
||||
{{end}} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="divider"></div> |
||||
|
||||
{{range $item := .WorkflowDispatchConfig.Inputs}} |
||||
<div class="ui field {{if .Required}}required{{end}}"> |
||||
{{if eq .Type "choice"}} |
||||
<label>{{.Description}}:</label> |
||||
<select class="ui selection type dropdown" name="{{.Name}}"> |
||||
{{range .Options}} |
||||
<option value="{{.}}" {{if eq $item.Default .}}selected{{end}} >{{.}}</option> |
||||
{{end}} |
||||
</select> |
||||
{{else if eq .Type "boolean"}} |
||||
<div class="ui inline checkbox"> |
||||
<label>{{.Description}}</label> |
||||
<input type="checkbox" name="{{.Name}}" {{if eq .Default "true"}}checked{{end}}> |
||||
</div> |
||||
{{else if eq .Type "number"}} |
||||
<label>{{.Description}}:</label> |
||||
<input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}> |
||||
{{else}} |
||||
<label>{{.Description}}:</label> |
||||
<input name="{{.Name}}" value="{{.Default}}" {{if .Required}}required{{end}}> |
||||
{{end}} |
||||
</div> |
||||
{{end}} |
||||
<button class="ui tiny primary button" type="submit">Submit</button> |
||||
</form> |
||||
</div> |
||||
</div> |
Loading…
Reference in new issue