diff --git a/pkg/github/client/client.go b/pkg/github/client/client.go
index ae0444f0..a44cff0c 100644
--- a/pkg/github/client/client.go
+++ b/pkg/github/client/client.go
@@ -177,7 +177,7 @@ func (client *Client) GetWorkflowUsage(ctx context.Context, owner, repo, workflo
}
var workflowRuns []*googlegithub.WorkflowRun
var err error
- workflowRuns, page, err = client.getWorkflowRuns(ctx, owner, repo, workflow, timeRange, page)
+ workflowRuns, page, err = client.getWorkflowRuns(ctx, owner, repo, workflow, "", timeRange, page)
if err != nil {
return models.WorkflowUsage{}, fmt.Errorf("fetching workflow runs: %w", err)
}
@@ -280,7 +280,29 @@ func (client *Client) getWorkflowUsage(ctx context.Context, owner, repo string,
return client.restClient.Actions.GetWorkflowUsageByFileName(ctx, owner, repo, workflow)
}
-func (client *Client) getWorkflowRuns(ctx context.Context, owner, repo, workflow string, timeRange backend.TimeRange, page int) ([]*googlegithub.WorkflowRun, int, error) {
+func (client *Client) GetWorkflowRuns(ctx context.Context, owner, repo, workflow string, branch string, timeRange backend.TimeRange) ([]*googlegithub.WorkflowRun, error) {
+ workflowRuns := []*googlegithub.WorkflowRun{}
+
+ page := 1
+ for {
+ if page == 0 {
+ break
+ }
+
+ workflowRunsPage, nextPage, err := client.getWorkflowRuns(ctx, owner, repo, workflow, branch, timeRange, page)
+ if err != nil {
+ return nil, fmt.Errorf("fetching workflow runs: %w", err)
+ }
+
+ workflowRuns = append(workflowRuns, workflowRunsPage...)
+
+ page = nextPage
+ }
+
+ return workflowRuns, nil
+}
+
+func (client *Client) getWorkflowRuns(ctx context.Context, owner, repo, workflow string, branch string, timeRange backend.TimeRange, page int) ([]*googlegithub.WorkflowRun, int, error) {
workflowID, _ := strconv.ParseInt(workflow, 10, 64)
workflowRuns := []*googlegithub.WorkflowRun{}
@@ -298,11 +320,13 @@ func (client *Client) getWorkflowRuns(ctx context.Context, owner, repo, workflow
runs, response, err = client.restClient.Actions.ListWorkflowRunsByID(ctx, owner, repo, workflowID, &googlegithub.ListWorkflowRunsOptions{
Created: created,
ListOptions: googlegithub.ListOptions{Page: page, PerPage: 100},
+ Branch: branch,
})
} else {
runs, response, err = client.restClient.Actions.ListWorkflowRunsByFileName(ctx, owner, repo, workflow, &googlegithub.ListWorkflowRunsOptions{
Created: created,
ListOptions: googlegithub.ListOptions{Page: page, PerPage: 100},
+ Branch: branch,
})
}
diff --git a/pkg/github/datasource.go b/pkg/github/datasource.go
index cedf52e5..3da6452c 100644
--- a/pkg/github/datasource.go
+++ b/pkg/github/datasource.go
@@ -180,6 +180,18 @@ func (d *Datasource) HandleWorkflowUsageQuery(ctx context.Context, query *models
return GetWorkflowUsage(ctx, d.client, opt, req.TimeRange)
}
+// HandleWorkflowRunsQuery is the query handler for listing workflow runs of a GitHub repository
+func (d *Datasource) HandleWorkflowRunsQuery(ctx context.Context, query *models.WorkflowRunsQuery, req backend.DataQuery) (dfutil.Framer, error) {
+ opt := models.WorkflowRunsOptions{
+ Repository: query.Repository,
+ Owner: query.Owner,
+ Workflow: query.Options.Workflow,
+ Branch: query.Options.Branch,
+ }
+
+ return GetWorkflowRuns(ctx, d.client, opt, req.TimeRange)
+}
+
// CheckHealth is the health check for GitHub
func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
_, err := GetAllRepositories(ctx, d.client, models.ListRepositoriesOptions{
diff --git a/pkg/github/query_handler.go b/pkg/github/query_handler.go
index 333b207e..68b0a333 100644
--- a/pkg/github/query_handler.go
+++ b/pkg/github/query_handler.go
@@ -57,6 +57,7 @@ func GetQueryHandlers(s *QueryHandler) *datasource.QueryTypeMux {
mux.HandleFunc(models.QueryTypeStargazers, s.HandleStargazers)
mux.HandleFunc(models.QueryTypeWorkflows, s.HandleWorkflows)
mux.HandleFunc(models.QueryTypeWorkflowUsage, s.HandleWorkflowUsage)
+ mux.HandleFunc(models.QueryTypeWorkflowRuns, s.HandleWorkflowRuns)
return mux
}
diff --git a/pkg/github/testdata/workflowRuns.golden.jsonc b/pkg/github/testdata/workflowRuns.golden.jsonc
new file mode 100644
index 00000000..ace5deab
--- /dev/null
+++ b/pkg/github/testdata/workflowRuns.golden.jsonc
@@ -0,0 +1,200 @@
+// 🌟 This was machine generated. Do not edit. 🌟
+//
+// Frame[0] {
+// "typeVersion": [
+// 0,
+// 0
+// ],
+// "preferredVisualisationType": "table"
+// }
+// Name: workflow_run
+// Dimensions: 13 Fields by 2 Rows
+// +----------------+-----------------+-------------------+-----------------+-------------------------------+-------------------------------+-----------------+-----------------+-----------------+------------------+-----------------+-------------------+------------------+
+// | Name: id | Name: name | Name: head_branch | Name: head_sha | Name: created_at | Name: updated_at | Name: html_url | Name: url | Name: status | Name: conclusion | Name: event | Name: workflow_id | Name: run_number |
+// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
+// | Type: []*int64 | Type: []*string | Type: []*string | Type: []*string | Type: []*time.Time | Type: []*time.Time | Type: []*string | Type: []*string | Type: []*string | Type: []*string | Type: []*string | Type: []*int64 | Type: []int64 |
+// +----------------+-----------------+-------------------+-----------------+-------------------------------+-------------------------------+-----------------+-----------------+-----------------+------------------+-----------------+-------------------+------------------+
+// | 2 | name_2 | head_branch_2 | head_sha_2 | 2013-02-03 00:00:00 +0000 UTC | 2013-02-04 00:00:00 +0000 UTC | html_url_2 | url_2 | status_2 | conclusion_2 | event_2 | 2 | 2 |
+// | 1 | name_1 | head_branch_1 | head_sha_1 | 2013-02-01 00:00:00 +0000 UTC | 2013-02-02 00:00:00 +0000 UTC | html_url_1 | url_1 | status_1 | conclusion_1 | event_1 | 1 | 1 |
+// +----------------+-----------------+-------------------+-----------------+-------------------------------+-------------------------------+-----------------+-----------------+-----------------+------------------+-----------------+-------------------+------------------+
+//
+//
+// 🌟 This was machine generated. Do not edit. 🌟
+{
+ "status": 200,
+ "frames": [
+ {
+ "schema": {
+ "name": "workflow_run",
+ "meta": {
+ "typeVersion": [
+ 0,
+ 0
+ ],
+ "preferredVisualisationType": "table"
+ },
+ "fields": [
+ {
+ "name": "id",
+ "type": "number",
+ "typeInfo": {
+ "frame": "int64",
+ "nullable": true
+ }
+ },
+ {
+ "name": "name",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string",
+ "nullable": true
+ }
+ },
+ {
+ "name": "head_branch",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string",
+ "nullable": true
+ }
+ },
+ {
+ "name": "head_sha",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string",
+ "nullable": true
+ }
+ },
+ {
+ "name": "created_at",
+ "type": "time",
+ "typeInfo": {
+ "frame": "time.Time",
+ "nullable": true
+ }
+ },
+ {
+ "name": "updated_at",
+ "type": "time",
+ "typeInfo": {
+ "frame": "time.Time",
+ "nullable": true
+ }
+ },
+ {
+ "name": "html_url",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string",
+ "nullable": true
+ }
+ },
+ {
+ "name": "url",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string",
+ "nullable": true
+ }
+ },
+ {
+ "name": "status",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string",
+ "nullable": true
+ }
+ },
+ {
+ "name": "conclusion",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string",
+ "nullable": true
+ }
+ },
+ {
+ "name": "event",
+ "type": "string",
+ "typeInfo": {
+ "frame": "string",
+ "nullable": true
+ }
+ },
+ {
+ "name": "workflow_id",
+ "type": "number",
+ "typeInfo": {
+ "frame": "int64",
+ "nullable": true
+ }
+ },
+ {
+ "name": "run_number",
+ "type": "number",
+ "typeInfo": {
+ "frame": "int64"
+ }
+ }
+ ]
+ },
+ "data": {
+ "values": [
+ [
+ 2,
+ 1
+ ],
+ [
+ "name_2",
+ "name_1"
+ ],
+ [
+ "head_branch_2",
+ "head_branch_1"
+ ],
+ [
+ "head_sha_2",
+ "head_sha_1"
+ ],
+ [
+ 1359849600000,
+ 1359676800000
+ ],
+ [
+ 1359936000000,
+ 1359763200000
+ ],
+ [
+ "html_url_2",
+ "html_url_1"
+ ],
+ [
+ "url_2",
+ "url_1"
+ ],
+ [
+ "status_2",
+ "status_1"
+ ],
+ [
+ "conclusion_2",
+ "conclusion_1"
+ ],
+ [
+ "event_2",
+ "event_1"
+ ],
+ [
+ 2,
+ 1
+ ],
+ [
+ 2,
+ 1
+ ]
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/pkg/github/workflows.go b/pkg/github/workflows.go
index 1c057517..ba29dd2e 100644
--- a/pkg/github/workflows.go
+++ b/pkg/github/workflows.go
@@ -185,3 +185,62 @@ func GetWorkflowUsage(ctx context.Context, client models.Client, opts models.Wor
return WorkflowUsageWrapper(data), nil
}
+
+// WorkflowRunsWrapper is a list of GitHub workflow runs
+type WorkflowRunsWrapper []*googlegithub.WorkflowRun
+
+// Frames converts the list of workflow runs to a Grafana DataFrame
+func (workflowRuns WorkflowRunsWrapper) Frames() data.Frames {
+ frame := data.NewFrame(
+ "workflow_run",
+ data.NewField("id", nil, []*int64{}),
+ data.NewField("name", nil, []*string{}),
+ data.NewField("head_branch", nil, []*string{}),
+ data.NewField("head_sha", nil, []*string{}),
+ data.NewField("created_at", nil, []*time.Time{}),
+ data.NewField("updated_at", nil, []*time.Time{}),
+ data.NewField("html_url", nil, []*string{}),
+ data.NewField("url", nil, []*string{}),
+ data.NewField("status", nil, []*string{}),
+ data.NewField("conclusion", nil, []*string{}),
+ data.NewField("event", nil, []*string{}),
+ data.NewField("workflow_id", nil, []*int64{}),
+ data.NewField("run_number", nil, []int64{}),
+ )
+
+ for _, workflowRun := range workflowRuns {
+ frame.InsertRow(
+ 0,
+ workflowRun.ID,
+ workflowRun.Name,
+ workflowRun.HeadBranch,
+ workflowRun.HeadSHA,
+ workflowRun.CreatedAt.GetTime(),
+ workflowRun.UpdatedAt.GetTime(),
+ workflowRun.HTMLURL,
+ workflowRun.URL,
+ workflowRun.Status,
+ workflowRun.Conclusion,
+ workflowRun.Event,
+ workflowRun.WorkflowID,
+ int64(*workflowRun.RunNumber),
+ )
+ }
+
+ frame.Meta = &data.FrameMeta{PreferredVisualization: data.VisTypeTable}
+ return data.Frames{frame}
+}
+
+// GetWorkflowRuns gets all workflows runs for a GitHub repository and workflow
+func GetWorkflowRuns(ctx context.Context, client models.Client, opts models.WorkflowRunsOptions, timeRange backend.TimeRange) (WorkflowRunsWrapper, error) {
+ if opts.Owner == "" || opts.Repository == "" {
+ return nil, nil
+ }
+
+ workflowRuns, err := client.GetWorkflowRuns(ctx, opts.Owner, opts.Repository, opts.Workflow, opts.Branch, timeRange)
+ if err != nil {
+ return nil, fmt.Errorf("listing workflows: opts=%+v %w", opts, err)
+ }
+
+ return WorkflowRunsWrapper(workflowRuns), nil
+}
diff --git a/pkg/github/workflows_handler.go b/pkg/github/workflows_handler.go
index 5fdabc7b..8215ca85 100644
--- a/pkg/github/workflows_handler.go
+++ b/pkg/github/workflows_handler.go
@@ -39,3 +39,19 @@ func (s *QueryHandler) HandleWorkflowUsage(ctx context.Context, req *backend.Que
Responses: processQueries(ctx, req, s.handleWorkflowUsageQuery),
}, nil
}
+
+func (s *QueryHandler) handleWorkflowRunsQuery(ctx context.Context, q backend.DataQuery) backend.DataResponse {
+ query := &models.WorkflowRunsQuery{}
+ if err := UnmarshalQuery(q.JSON, query); err != nil {
+ return *err
+ }
+
+ return dfutil.FrameResponseWithError(s.Datasource.HandleWorkflowRunsQuery(ctx, query, q))
+}
+
+// HandleWorkflowRuns handles the plugin query for GitHub workflows
+func (s *QueryHandler) HandleWorkflowRuns(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
+ return &backend.QueryDataResponse{
+ Responses: processQueries(ctx, req, s.handleWorkflowRunsQuery),
+ }, nil
+}
diff --git a/pkg/github/workflows_test.go b/pkg/github/workflows_test.go
index 47056f5a..ec370fde 100644
--- a/pkg/github/workflows_test.go
+++ b/pkg/github/workflows_test.go
@@ -244,3 +244,54 @@ func TestWorkflowUsageDataframe(t *testing.T) {
testutil.CheckGoldenFramer(t, "workflowUsage", usage)
}
+
+func TestWorkflowRunsDataFrame(t *testing.T) {
+ t.Parallel()
+
+ createdAt1, err := time.Parse("2006-Jan-02", "2013-Feb-01")
+ assert.NoError(t, err)
+
+ updatedAt1, err := time.Parse("2006-Jan-02", "2013-Feb-02")
+ assert.NoError(t, err)
+
+ createdAt2, err := time.Parse("2006-Jan-02", "2013-Feb-03")
+ assert.NoError(t, err)
+
+ updatedAt2, err := time.Parse("2006-Jan-02", "2013-Feb-04")
+ assert.NoError(t, err)
+
+ workflowRuns := WorkflowRunsWrapper([]*googlegithub.WorkflowRun{
+ {
+ ID: ptr(int64(1)),
+ Name: ptr("name_1"),
+ HeadBranch: ptr("head_branch_1"),
+ HeadSHA: ptr("head_sha_1"),
+ CreatedAt: &googlegithub.Timestamp{Time: createdAt1},
+ UpdatedAt: &googlegithub.Timestamp{Time: updatedAt1},
+ HTMLURL: ptr("html_url_1"),
+ URL: ptr("url_1"),
+ Status: ptr("status_1"),
+ Conclusion: ptr("conclusion_1"),
+ Event: ptr("event_1"),
+ WorkflowID: ptr(int64(1)),
+ RunNumber: ptr(int(1)),
+ },
+ {
+ ID: ptr(int64(2)),
+ Name: ptr("name_2"),
+ HeadBranch: ptr("head_branch_2"),
+ HeadSHA: ptr("head_sha_2"),
+ CreatedAt: &googlegithub.Timestamp{Time: createdAt2},
+ UpdatedAt: &googlegithub.Timestamp{Time: updatedAt2},
+ HTMLURL: ptr("html_url_2"),
+ URL: ptr("url_2"),
+ Status: ptr("status_2"),
+ Conclusion: ptr("conclusion_2"),
+ Event: ptr("event_2"),
+ WorkflowID: ptr(int64(2)),
+ RunNumber: ptr(int(2)),
+ },
+ })
+
+ testutil.CheckGoldenFramer(t, "workflowRuns", workflowRuns)
+}
diff --git a/pkg/models/client.go b/pkg/models/client.go
index e1d7165e..92c12874 100644
--- a/pkg/models/client.go
+++ b/pkg/models/client.go
@@ -13,4 +13,5 @@ type Client interface {
Query(ctx context.Context, q interface{}, variables map[string]interface{}) error
ListWorkflows(ctx context.Context, owner, repo string, opts *googlegithub.ListOptions) (*googlegithub.Workflows, *googlegithub.Response, error)
GetWorkflowUsage(ctx context.Context, owner, repo, workflow string, timeRange backend.TimeRange) (WorkflowUsage, error)
+ GetWorkflowRuns(ctx context.Context, owner, repo, workflow string, branch string, timeRange backend.TimeRange) ([]*googlegithub.WorkflowRun, error)
}
diff --git a/pkg/models/query.go b/pkg/models/query.go
index 09332020..17e5aad7 100644
--- a/pkg/models/query.go
+++ b/pkg/models/query.go
@@ -37,6 +37,8 @@ const (
QueryTypeWorkflows = "Workflows"
// QueryTypeWorkflowUsage is used when querying a specific workflow usage
QueryTypeWorkflowUsage = "Workflow_Usage"
+ // QueryTypeWorkflowRuns is used when querying workflow runs for a repository
+ QueryTypeWorkflowRuns = "Workflow_Runs"
)
// Query refers to the structure of a query built using the QueryEditor.
@@ -129,3 +131,9 @@ type WorkflowUsageQuery struct {
Query
Options WorkflowUsageOptions `json:"options"`
}
+
+// WorkflowRunsQuery is used when querying workflow runs for a repository
+type WorkflowRunsQuery struct {
+ Query
+ Options WorkflowRunsOptions `json:"options"`
+}
diff --git a/pkg/models/workflows.go b/pkg/models/workflows.go
index a9120f68..6afdaccf 100644
--- a/pkg/models/workflows.go
+++ b/pkg/models/workflows.go
@@ -34,8 +34,13 @@ type WorkflowUsageOptions struct {
// Workflow is the id or the workflow file name.
Workflow string `json:"workflow"`
+
+ // Branch is the branch to filter the runs by.
+ Branch string `json:"branch"`
}
+type WorkflowRunsOptions = WorkflowUsageOptions
+
// WorkflowUsage contains a specific workflow usage information.
type WorkflowUsage struct {
CostUSD float64
diff --git a/pkg/plugin/datasource.go b/pkg/plugin/datasource.go
index 9eee382a..abc5e93a 100644
--- a/pkg/plugin/datasource.go
+++ b/pkg/plugin/datasource.go
@@ -25,6 +25,7 @@ type Datasource interface {
HandleStargazersQuery(context.Context, *models.StargazersQuery, backend.DataQuery) (dfutil.Framer, error)
HandleWorkflowsQuery(context.Context, *models.WorkflowsQuery, backend.DataQuery) (dfutil.Framer, error)
HandleWorkflowUsageQuery(context.Context, *models.WorkflowUsageQuery, backend.DataQuery) (dfutil.Framer, error)
+ HandleWorkflowRunsQuery(context.Context, *models.WorkflowRunsQuery, backend.DataQuery) (dfutil.Framer, error)
CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error)
QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error)
}
diff --git a/pkg/plugin/datasource_caching.go b/pkg/plugin/datasource_caching.go
index 87800fa7..8956c232 100644
--- a/pkg/plugin/datasource_caching.go
+++ b/pkg/plugin/datasource_caching.go
@@ -232,6 +232,16 @@ func (c *CachedDatasource) HandleWorkflowUsageQuery(ctx context.Context, q *mode
return c.saveCache(req, f, err)
}
+// HandleWorkflowRunsQuery is the cache wrapper for the workflows runs query handler
+func (c *CachedDatasource) HandleWorkflowRunsQuery(ctx context.Context, q *models.WorkflowRunsQuery, req backend.DataQuery) (dfutil.Framer, error) {
+ if value, err := c.getCache(req); err == nil {
+ return value, err
+ }
+
+ f, err := c.datasource.HandleWorkflowRunsQuery(ctx, q, req)
+ return c.saveCache(req, f, err)
+}
+
// CheckHealth forwards the request to the datasource and does not perform any caching
func (c *CachedDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
return c.datasource.CheckHealth(ctx, req)
diff --git a/pkg/testutil/client.go b/pkg/testutil/client.go
index 4368ebcf..2ad9e892 100644
--- a/pkg/testutil/client.go
+++ b/pkg/testutil/client.go
@@ -60,3 +60,8 @@ func (c *TestClient) ListWorkflows(ctx context.Context, owner, repo string, opts
func (c *TestClient) GetWorkflowUsage(ctx context.Context, owner, repo, workflow string, timeRange backend.TimeRange) (models.WorkflowUsage, error) {
panic("unimplemented")
}
+
+// GetWorkflowRuns is not implemented because it is not being used at the moment.
+func (c *TestClient) GetWorkflowRuns(ctx context.Context, owner, repo, workflow string, branch string, timeRange backend.TimeRange) ([]*googlegithub.WorkflowRun, error) {
+ panic("unimplemented")
+}
diff --git a/src/constants.ts b/src/constants.ts
index 95c99c52..7b1cc805 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -17,6 +17,7 @@ export enum QueryType {
Stargazers = 'Stargazers',
Workflows = 'Workflows',
Workflow_Usage = 'Workflow_Usage',
+ Workflow_Runs = 'Workflow_Runs',
}
export const DefaultQueryType = QueryType.Issues;
diff --git a/src/types/query.ts b/src/types/query.ts
index f408b029..1a9d9c0d 100644
--- a/src/types/query.ts
+++ b/src/types/query.ts
@@ -18,7 +18,8 @@ export interface GitHubQuery extends Indexable, DataQuery, RepositoryOptions {
| ContributorsOptions
| ProjectsOptions
| WorkflowsOptions
- | WorkflowUsageOptions;
+ | WorkflowUsageOptions
+ | WorkflowRunsOptions;
}
export interface Label {
@@ -66,6 +67,11 @@ export interface WorkflowUsageOptions extends Indexable {
workflowID?: number;
}
+export interface WorkflowRunsOptions extends Indexable {
+ workflowID?: string;
+ branch?: string;
+}
+
export interface PackagesOptions extends Indexable {
names?: string;
packageType?: PackageType;
diff --git a/src/views/QueryEditor.tsx b/src/views/QueryEditor.tsx
index 5a47a3dd..0ed6e7a8 100644
--- a/src/views/QueryEditor.tsx
+++ b/src/views/QueryEditor.tsx
@@ -20,6 +20,7 @@ import QueryEditorVulnerabilities from './QueryEditorVulnerabilities';
import QueryEditorProjects from './QueryEditorProjects';
import QueryEditorWorkflows from './QueryEditorWorkflows';
import QueryEditorWorkflowUsage from './QueryEditorWorkflowUsage';
+import QueryEditorWorkflowRuns from './QueryEditorWorkflowRuns';
import { QueryType, DefaultQueryType } from '../constants';
import type { GitHubQuery } from '../types/query';
import type { GitHubDataSourceOptions } from '../types/config';
@@ -101,6 +102,11 @@ const queryEditors: {
),
},
+ [QueryType.Workflow_Runs]: {
+ component: (props: Props, onChange: (val: any) => void) => (
+
+ ),
+ },
};
/* eslint-enable react/display-name */
diff --git a/src/views/QueryEditorWorkflowRuns.tsx b/src/views/QueryEditorWorkflowRuns.tsx
new file mode 100644
index 00000000..55072671
--- /dev/null
+++ b/src/views/QueryEditorWorkflowRuns.tsx
@@ -0,0 +1,54 @@
+import React, { useState } from 'react';
+import { Input, InlineField } from '@grafana/ui';
+import { RightColumnWidth, LeftColumnWidth } from './QueryEditor';
+import type { WorkflowRunsOptions } from 'types/query';
+
+interface Props extends WorkflowRunsOptions {
+ onChange: (value: WorkflowRunsOptions) => void;
+}
+
+const QueryEditorWorkflowRuns = (props: Props) => {
+ const [workflow, setWorkflow] = useState(props.workflow);
+ const [branch, setBranch] = useState(props.branch);
+
+ return (
+ <>
+
+ setWorkflow(el.currentTarget.value)}
+ onBlur={(el) =>
+ props.onChange({
+ ...props,
+ workflow: el.currentTarget.value,
+ })
+ }
+ />
+
+
+ setBranch(el.currentTarget.value)}
+ onBlur={(el) =>
+ props.onChange({
+ ...props,
+ branch: el.currentTarget.value,
+ })
+ }
+ />
+
+ >
+ );
+};
+
+export default QueryEditorWorkflowRuns;