+
+ {{ctx.Locale.Tr "actions.workflow.dispatch.trigger_found"}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/repo/actions/list.tmpl b/templates/repo/actions/list.tmpl
index b66d0e360a..263530f9a7 100644
--- a/templates/repo/actions/list.tmpl
+++ b/templates/repo/actions/list.tmpl
@@ -76,6 +76,11 @@
{{end}}
+
+ {{if $.CurWorkflowDispatch}}
+ {{template "repo/actions/dispatch" .}}
+ {{end}}
+
{{template "repo/actions/runs_list" .}}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index f11e67d4ad..dacec3ed1a 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -4239,6 +4239,56 @@
}
}
},
+ "/repos/{owner}/{repo}/actions/workflows/{workflowname}/dispatches": {
+ "post": {
+ "consumes": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Dispatches a workflow",
+ "operationId": "DispatchWorkflow",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "owner of the repo",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repo",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the workflow",
+ "name": "workflowname",
+ "in": "path",
+ "required": true
+ },
+ {
+ "name": "body",
+ "in": "body",
+ "schema": {
+ "$ref": "#/definitions/DispatchWorkflowOption"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "$ref": "#/responses/empty"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/repos/{owner}/{repo}/activities/feeds": {
"get": {
"produces": [
@@ -20902,6 +20952,29 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
+ "DispatchWorkflowOption": {
+ "description": "DispatchWorkflowOption options when dispatching a workflow",
+ "type": "object",
+ "required": [
+ "ref"
+ ],
+ "properties": {
+ "inputs": {
+ "description": "Input keys and values configured in the workflow file.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ },
+ "x-go-name": "Inputs"
+ },
+ "ref": {
+ "description": "Git reference for the workflow",
+ "type": "string",
+ "x-go-name": "Ref"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
"EditAttachmentOptions": {
"description": "EditAttachmentOptions options for editing attachments",
"type": "object",
@@ -26627,7 +26700,7 @@
"parameterBodies": {
"description": "parameterBodies",
"schema": {
- "$ref": "#/definitions/UpdateVariableOption"
+ "$ref": "#/definitions/DispatchWorkflowOption"
}
},
"redirect": {
diff --git a/tests/e2e/actions.test.e2e.js b/tests/e2e/actions.test.e2e.js
new file mode 100644
index 0000000000..d7ca75bfc6
--- /dev/null
+++ b/tests/e2e/actions.test.e2e.js
@@ -0,0 +1,74 @@
+// @ts-check
+import {test, expect} from '@playwright/test';
+import {login_user, load_logged_in_context} from './utils_e2e.js';
+
+test.beforeAll(async ({browser}, workerInfo) => {
+ await login_user(browser, workerInfo, 'user2');
+});
+
+test('Test workflow dispatch present', async ({browser}, workerInfo) => {
+ const context = await load_logged_in_context(browser, workerInfo, 'user2');
+ /** @type {import('@playwright/test').Page} */
+ const page = await context.newPage();
+
+ await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0');
+
+ await expect(page.getByText('This workflow has a workflow_dispatch event trigger.')).toBeVisible();
+
+ const run_workflow_btn = page.locator('#workflow_dispatch_dropdown>button');
+ await expect(run_workflow_btn).toBeVisible();
+
+ const menu = page.locator('#workflow_dispatch_dropdown>.menu');
+ await expect(menu).toBeHidden();
+ await run_workflow_btn.click();
+ await expect(menu).toBeVisible();
+});
+
+test('Test workflow dispatch error: missing inputs', async ({browser}, workerInfo) => {
+ test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari; see https://codeberg.org/forgejo/forgejo/pulls/3334#issuecomment-2033383');
+
+ const context = await load_logged_in_context(browser, workerInfo, 'user2');
+ /** @type {import('@playwright/test').Page} */
+ const page = await context.newPage();
+
+ await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0');
+ await page.waitForLoadState('networkidle');
+
+ await page.locator('#workflow_dispatch_dropdown>button').click();
+
+ await page.waitForTimeout(1000);
+
+ // Remove the required attribute so we can trigger the error message!
+ await page.evaluate(() => {
+ // eslint-disable-next-line no-undef
+ const elem = document.querySelector('input[name="inputs[string2]"]');
+ elem?.removeAttribute('required');
+ });
+
+ await page.locator('#workflow-dispatch-submit').click();
+ await page.waitForLoadState('networkidle');
+
+ await expect(page.getByText('Require value for input "String w/o. default".')).toBeVisible();
+});
+
+test('Test workflow dispatch success', async ({browser}, workerInfo) => {
+ test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari; see https://codeberg.org/forgejo/forgejo/pulls/3334#issuecomment-2033383');
+
+ const context = await load_logged_in_context(browser, workerInfo, 'user2');
+ /** @type {import('@playwright/test').Page} */
+ const page = await context.newPage();
+
+ await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0');
+ await page.waitForLoadState('networkidle');
+
+ await page.locator('#workflow_dispatch_dropdown>button').click();
+ await page.waitForTimeout(1000);
+
+ await page.type('input[name="inputs[string2]"]', 'abc');
+ await page.locator('#workflow-dispatch-submit').click();
+ await page.waitForLoadState('networkidle');
+
+ await expect(page.getByText('Workflow run was successfully requested.')).toBeVisible();
+
+ await expect(page.locator('.run-list>:first-child .run-list-meta', {hasText: 'now'})).toBeVisible();
+});
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/HEAD b/tests/gitea-repositories-meta/user2/test_workflows.git/HEAD
new file mode 100644
index 0000000000..b870d82622
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/test_workflows.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/main
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/config b/tests/gitea-repositories-meta/user2/test_workflows.git/config
new file mode 100644
index 0000000000..07d359d07c
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/test_workflows.git/config
@@ -0,0 +1,4 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/description b/tests/gitea-repositories-meta/user2/test_workflows.git/description
new file mode 100644
index 0000000000..498b267a8c
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/test_workflows.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/info/exclude b/tests/gitea-repositories-meta/user2/test_workflows.git/info/exclude
new file mode 100644
index 0000000000..a5196d1be8
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/test_workflows.git/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/objects/26/c8f930a36802d9cfb9785ca88704b1f52347aa b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/26/c8f930a36802d9cfb9785ca88704b1f52347aa
new file mode 100644
index 0000000000..439b74accc
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/26/c8f930a36802d9cfb9785ca88704b1f52347aa differ
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/objects/2d/7f57e0a452699a5d2da0e42dcb2375de546c0a b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/2d/7f57e0a452699a5d2da0e42dcb2375de546c0a
new file mode 100644
index 0000000000..ac621857bd
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/2d/7f57e0a452699a5d2da0e42dcb2375de546c0a differ
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/objects/2d/89b2afa3e19e924330b4307a181714a4179010 b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/2d/89b2afa3e19e924330b4307a181714a4179010
new file mode 100644
index 0000000000..156f4eedff
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/2d/89b2afa3e19e924330b4307a181714a4179010 differ
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
new file mode 100644
index 0000000000..adf64119a3
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/objects/77/4f93df12d14931ea93259ae93418da4482fcc1 b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/77/4f93df12d14931ea93259ae93418da4482fcc1
new file mode 100644
index 0000000000..036a82d075
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/77/4f93df12d14931ea93259ae93418da4482fcc1 differ
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/objects/96/63cd4783a54f3e57b2dd908b077cf8126c826c b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/96/63cd4783a54f3e57b2dd908b077cf8126c826c
new file mode 100644
index 0000000000..c07ce1e660
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_workflows.git/objects/96/63cd4783a54f3e57b2dd908b077cf8126c826c differ
diff --git a/tests/gitea-repositories-meta/user2/test_workflows.git/packed-refs b/tests/gitea-repositories-meta/user2/test_workflows.git/packed-refs
new file mode 100644
index 0000000000..24867ee38e
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/test_workflows.git/packed-refs
@@ -0,0 +1,3 @@
+# pack-refs with: peeled fully-peeled sorted
+774f93df12d14931ea93259ae93418da4482fcc1 refs/heads/main
+774f93df12d14931ea93259ae93418da4482fcc1 refs/heads/master
diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go
index 9359dc09b4..5309ebef2a 100644
--- a/tests/integration/actions_trigger_test.go
+++ b/tests/integration/actions_trigger_test.go
@@ -396,3 +396,46 @@ func TestCreateDeleteRefEvent(t *testing.T) {
assert.NotNil(t, run)
})
}
+
+func TestWorkflowDispatchEvent(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+
+ // create the repo
+ repo, sha, f := CreateDeclarativeRepo(t, user2, "repo-workflow-dispatch",
+ []unit_model.Type{unit_model.TypeActions}, nil,
+ []*files_service.ChangeRepoFile{
+ {
+ Operation: "create",
+ TreePath: ".gitea/workflows/dispatch.yml",
+ ContentReader: strings.NewReader(
+ "name: test\n" +
+ "on: [workflow_dispatch]\n" +
+ "jobs:\n" +
+ " test:\n" +
+ " runs-on: ubuntu-latest\n" +
+ " steps:\n" +
+ " - run: echo helloworld\n",
+ ),
+ },
+ },
+ )
+ defer f()
+
+ gitRepo, err := gitrepo.OpenRepository(db.DefaultContext, repo)
+ assert.NoError(t, err)
+ defer gitRepo.Close()
+
+ workflow, err := actions_service.GetWorkflowFromCommit(gitRepo, sha, "dispatch.yml")
+ assert.NoError(t, err)
+
+ inputGetter := func(key string) string {
+ return ""
+ }
+
+ err = workflow.Dispatch(db.DefaultContext, inputGetter, repo, user2)
+ assert.NoError(t, err)
+
+ assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID}))
+ })
+}
diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go
index 4c2c43b197..6e16a12f22 100644
--- a/tests/integration/api_repo_test.go
+++ b/tests/integration/api_repo_test.go
@@ -95,9 +95,9 @@ func TestAPISearchRepo(t *testing.T) {
}{
{
name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{
- nil: {count: 36},
- user: {count: 36},
- user2: {count: 36},
+ nil: {count: 37},
+ user: {count: 37},
+ user2: {count: 37},
},
},
{
diff --git a/web_src/css/actions.css b/web_src/css/actions.css
index 0ab09f537a..c89a70ec04 100644
--- a/web_src/css/actions.css
+++ b/web_src/css/actions.css
@@ -81,3 +81,16 @@
max-width: 110px;
}
}
+
+#workflow_dispatch_dropdown {
+ min-width: min-content;
+}
+#workflow_dispatch_dropdown > button {
+ white-space: nowrap;
+}
+@media (max-width: 640px) or (767.98px < width < 854px) {
+ #workflow_dispatch_dropdown .menu {
+ left: auto;
+ right: 0;
+ }
+}