package db import ( "context" "encoding/json" "strconv" "strings" "time" "tangled.org/core/api/tangled" "tangled.org/core/spindle/models" ) func (d *DB) QueryPipelines(ctx context.Context, repoDid string, commits []string, cursor string, limit int) ([]*tangled.CiDefs_Pipeline, string, int64, error) { if limit <= 0 { limit = 30 } var query string var args []interface{} query = ` select rkey, event, created from events where nsid = 'sh.tangled.pipeline' and coalesce(json_extract(event, '$.triggerMetadata.repo.repoDid'), json_extract(event, '$.triggerMetadata.repo.did')) = ? ` args = append(args, repoDid) if len(commits) > 0 { placeholders := make([]string, len(commits)) for i := range commits { placeholders[i] = "?" args = append(args, commits[i]) } query += " and json_extract(event, '$.triggerMetadata.push.newSha') in (" + strings.Join(placeholders, ",") + ")" } if cursor != "" { if cVal, err := strconv.ParseInt(cursor, 10, 64); err == nil { query += " and created < ?" args = append(args, cVal) } } // First get total count var total int64 countQuery := "select count(*) from (" + query + ")" if err := d.QueryRowContext(ctx, countQuery, args...).Scan(&total); err != nil { return nil, "", 0, err } query += " order by created desc limit ?" args = append(args, limit) rows, err := d.QueryContext(ctx, query, args...) if err != nil { return nil, "", 0, err } defer rows.Close() var pipelines []*tangled.CiDefs_Pipeline var lastCreated int64 for rows.Next() { var rkey, eventJson string var created int64 if err := rows.Scan(&rkey, &eventJson, &created); err != nil { return nil, "", 0, err } lastCreated = created var rawPipeline tangled.Pipeline if err := json.Unmarshal([]byte(eventJson), &rawPipeline); err != nil { continue } p, err := d.mapToCiDefsPipeline(ctx, rkey, created, rawPipeline) if err != nil { return nil, "", 0, err } pipelines = append(pipelines, p) } nextCursor := "" if len(pipelines) == limit { nextCursor = strconv.FormatInt(lastCreated, 10) } return pipelines, nextCursor, total, nil } func (d *DB) GetPipeline(ctx context.Context, rkey string) (*tangled.CiDefs_Pipeline, error) { var eventJson string var created int64 err := d.QueryRowContext(ctx, ` select event, created from events where nsid = 'sh.tangled.pipeline' and rkey = ? `, rkey, ).Scan(&eventJson, &created) if err != nil { return nil, err } var rawPipeline tangled.Pipeline if err := json.Unmarshal([]byte(eventJson), &rawPipeline); err != nil { return nil, err } return d.mapToCiDefsPipeline(ctx, rkey, created, rawPipeline) } func (d *DB) mapToCiDefsPipeline(ctx context.Context, rkey string, created int64, raw tangled.Pipeline) (*tangled.CiDefs_Pipeline, error) { createdAtStr := time.Unix(0, created).Format(time.RFC3339) var repoDidStr string if raw.TriggerMetadata != nil && raw.TriggerMetadata.Repo != nil { if raw.TriggerMetadata.Repo.RepoDid != nil { repoDidStr = *raw.TriggerMetadata.Repo.RepoDid } else { repoDidStr = raw.TriggerMetadata.Repo.Did } } commitSha := "" var trigger tangled.CiDefs_Pipeline_Trigger if raw.TriggerMetadata != nil { switch raw.TriggerMetadata.Kind { case "push": if raw.TriggerMetadata.Push != nil { commitSha = raw.TriggerMetadata.Push.NewSha trigger.CiTrigger_Push = &tangled.CiTrigger_Push{ NewSha: raw.TriggerMetadata.Push.NewSha, OldSha: raw.TriggerMetadata.Push.OldSha, Ref: raw.TriggerMetadata.Push.Ref, } } case "pullRequest": if raw.TriggerMetadata.PullRequest != nil { commitSha = raw.TriggerMetadata.PullRequest.SourceSha trigger.CiTrigger_PullRequest = &tangled.CiTrigger_PullRequest{ Action: raw.TriggerMetadata.PullRequest.Action, SourceBranch: &raw.TriggerMetadata.PullRequest.SourceBranch, SourceSha: raw.TriggerMetadata.PullRequest.SourceSha, TargetBranch: raw.TriggerMetadata.PullRequest.TargetBranch, } } case "manual": if raw.TriggerMetadata.Manual != nil { commitSha = raw.TriggerMetadata.Manual.Sha trigger.CiTrigger_Manual = &tangled.CiTrigger_Manual{} } } } var workflows []*tangled.CiDefs_Workflow for _, wf := range raw.Workflows { status := "pending" var startedAt, finishedAt, wfError *string if raw.TriggerMetadata != nil && raw.TriggerMetadata.Repo != nil { wfId := models.WorkflowId{ PipelineId: models.PipelineId{ Knot: raw.TriggerMetadata.Repo.Knot, Rkey: rkey, }, Name: wf.Name, } wfStatus, err := d.GetStatus(wfId) if err == nil && wfStatus != nil { status = wfStatus.Status startedAt, finishedAt = d.GetWorkflowTimes(wfId) wfError = wfStatus.Error } } workflows = append(workflows, &tangled.CiDefs_Workflow{ Id: wf.Name, Name: wf.Name, Status: status, StartedAt: startedAt, FinishedAt: finishedAt, Error: wfError, }) } return &tangled.CiDefs_Pipeline{ Id: rkey, Commit: commitSha, Repo: &repoDidStr, CreatedAt: &createdAtStr, Trigger: &trigger, Workflows: workflows, }, nil } func (d *DB) GetWorkflowTimes(workflowId models.WorkflowId) (startedAt, finishedAt *string) { pipelineAtUri := workflowId.PipelineId.AtUri() _ = d.QueryRow( ` select min(case when json_extract(event, '$.status') = 'running' then json_extract(event, '$.createdAt') end), max(case when json_extract(event, '$.status') in ('success', 'failed', 'timeout', 'cancelled') then json_extract(event, '$.createdAt') end) from events where nsid = ? and json_extract(event, '$.pipeline') = ? and json_extract(event, '$.workflow') = ? `, tangled.PipelineStatusNSID, string(pipelineAtUri), workflowId.Name, ).Scan(&startedAt, &finishedAt) return }