Skip to content

Commit

Permalink
feat(author): return user who triggered the workflow
Browse files Browse the repository at this point in the history
by retrieving the commit's author
  • Loading branch information
tsimbalar committed Jan 14, 2021
1 parent d7e378b commit a68f7ad
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 9 deletions.
13 changes: 12 additions & 1 deletion src/api/controllers/BuildInfoController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,26 @@ export class BuildInfoController extends Controller {
}

private mapToBuild(run: WorkflowRun): catlightCore.Build {
return {
let result: catlightCore.Build = {
id: run.id,
startTime: run.startTime,
status: run.status,
finishTime: run.finishTime,
name: run.name,
webUrl: run.webUrl,

// TODO: contributors, triggeredByUser
};
if (run.mainAuthor) {
result = {
...result,
triggeredByUser: {
id: run.mainAuthor.login,
name: run.mainAuthor.name,
},
};
}
return result;
}

private mapToBuildDefinitionMetadata(
Expand Down
6 changes: 5 additions & 1 deletion src/composition-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MetaInfo, meta as metaFromPackageJson } from './meta';
import { BearerAuthenticationProvider } from './api/auth/BearerAuthenticationProvider';
import { BuildInfoController } from './api/controllers/BuildInfoController';
import { CachedRepoRepository } from './infra/caching/CachedRepoRepository';
import { CommitAuthorRepository } from './infra/github/CommitAuthorRepository';
import { Controller } from '@tsoa/runtime';
import { DiagnosticsController } from './api/controllers/DiagnosticsController';
import { DynamicBuildInfoController } from './api/controllers/DynamicBuildInfoController';
Expand Down Expand Up @@ -52,7 +53,10 @@ export class CompositionRoot implements IControllerFactory {
return new CompositionRoot(settings, metaFromPackageJson, {
userRepo: new UserRepository(octokitFactory),
repoRepo: new RepoRepository(octokitFactory),
workflowRunRepo: new WorkflowRunRepository(octokitFactory),
workflowRunRepo: new WorkflowRunRepository(
octokitFactory,
new CommitAuthorRepository(octokitFactory)
),
});
}

Expand Down
5 changes: 5 additions & 0 deletions src/domain/IWorkflowRunRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export type WorkflowRunStatus =
| 'Failed'
| 'Canceled';

export interface WorkflowRunAuthor {
readonly login: string;
readonly name: string;
}
export interface WorkflowRun {
readonly id: string;
readonly name?: string;
Expand All @@ -16,6 +20,7 @@ export interface WorkflowRun {
readonly event: string;
readonly startTime: Date;
readonly finishTime?: Date;
readonly mainAuthor?: WorkflowRunAuthor;
}

export interface WorflowRunFilter {
Expand Down
42 changes: 42 additions & 0 deletions src/infra/github/CommitAuthorRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { OctokitFactory } from './OctokitFactory';
import { RepoName } from '../../domain/IRepoRepository';

export interface CommitAuthor {
readonly login: string;
readonly name?: string;
}
export interface ICommitAuthorRepository {
getAuthorForCommit(
token: string,
repoName: RepoName,
commitId: string
): Promise<CommitAuthor | null>;
}

export class CommitAuthorRepository implements ICommitAuthorRepository {
public constructor(private readonly octokitFactory: OctokitFactory) {}

public async getAuthorForCommit(
token: string,
repoName: RepoName,
commitId: string
): Promise<CommitAuthor | null> {
const octokit = this.octokitFactory(token);

const response = await octokit.repos.getCommit({
owner: repoName.owner,
repo: repoName.name,
ref: commitId,
});

if (!response.data.author) {
return null;
}

const result = {
login: response.data.author.login,
name: response.data.commit.author?.name,
};
return result;
}
}
27 changes: 26 additions & 1 deletion src/infra/github/WorkflowRunRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ import {
IWorkflowRunRepository,
WorflowRunFilter,
WorkflowRun,
WorkflowRunAuthor,
WorkflowRunStatus,
WorkflowRunsPerBranch,
} from '../../domain/IWorkflowRunRepository';
import { ICommitAuthorRepository } from './CommitAuthorRepository';
import { Octokit } from '@octokit/rest';
import { OctokitFactory } from './OctokitFactory';
import { RepoName } from '../../domain/IRepoRepository';
import { parseISO } from 'date-fns';

export class WorkflowRunRepository implements IWorkflowRunRepository {
public constructor(private readonly octokitFactory: OctokitFactory) {}
public constructor(
private readonly octokitFactory: OctokitFactory,
private readonly commitAuthorRepo: ICommitAuthorRepository
) {}

public async getLatestRunsForWorkflow(
token: string,
Expand Down Expand Up @@ -46,13 +52,31 @@ export class WorkflowRunRepository implements IWorkflowRunRepository {

const currentForBranch = result.get(branchKey) || [];

const isLatestRunInThisBranch = currentForBranch.length === 0;

if (currentForBranch.length >= filter.maxRunsPerBranch) {
// skipping this run because we already have enough builds for this branch
// eslint-disable-next-line no-continue
continue;
}

const status = this.parseWorkflowRunStatus(run.status, run.conclusion);
let author: WorkflowRunAuthor | undefined;
if (isLatestRunInThisBranch) {
// eslint-disable-next-line no-await-in-loop
const commitAuthor = await this.commitAuthorRepo.getAuthorForCommit(
token,
repoName,
run.head_commit.id
);

if (commitAuthor) {
author = {
login: commitAuthor.login,
name: commitAuthor.name ?? commitAuthor.login,
};
}
}
const workflowRun: WorkflowRun = {
id: run.id.toString(),
webUrl: run.html_url,
Expand All @@ -61,6 +85,7 @@ export class WorkflowRunRepository implements IWorkflowRunRepository {
status,
finishTime: parseISO(run.updated_at),
event: run.event,
mainAuthor: author,
};

result.set(branchKey, [workflowRun, ...currentForBranch]);
Expand Down
56 changes: 50 additions & 6 deletions src/infra/github/__tests__/WorkflowRunRepository.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import {
CommitAuthor,
CommitAuthorRepository,
ICommitAuthorRepository,
} from '../CommitAuthorRepository';
import { THIS_REPO_MAIN_WORKFLOW, THIS_REPO_NAME } from '../__testTools__/TestConstants';
import { WorkflowRun, WorkflowRunAuthor } from '../../../domain/IWorkflowRunRepository';
import { RepoName } from '../../../domain/IRepoRepository';
import { WorkflowRun } from '../../../domain/IWorkflowRunRepository';
import { WorkflowRunRepository } from '../WorkflowRunRepository';
import { getOctokitFactory } from '../OctokitFactory';
import { testCredentials } from '../__testTools__/TestCredentials';

class EmptyCommitAuthorRepo implements ICommitAuthorRepository {
public async getAuthorForCommit(
token: string,
repoName: RepoName,
commitId: string
): Promise<CommitAuthor | null> {
return null;
}
}
describe('WorkflowRunRepository', () => {
const octokitFactory = getOctokitFactory({
version: 'v0-tests',
buildInfo: {},
});
const emptyCommitAutorRepo = new EmptyCommitAuthorRepo();
describe('getLatestRunsForWorkflow', () => {
test('should retrieve runs of public repo', async () => {
const sut = new WorkflowRunRepository(octokitFactory);
const sut = new WorkflowRunRepository(octokitFactory, emptyCommitAutorRepo);

const actual = await sut.getLatestRunsForWorkflow(
testCredentials.PAT_NO_SCOPE,
Expand Down Expand Up @@ -42,8 +57,37 @@ describe('WorkflowRunRepository', () => {
});
});

test('should retrieve authors of commits on public repo', async () => {
const sut = new WorkflowRunRepository(
octokitFactory,
new CommitAuthorRepository(octokitFactory)
);

const actual = await sut.getLatestRunsForWorkflow(
testCredentials.PAT_NO_SCOPE,
THIS_REPO_NAME,
THIS_REPO_MAIN_WORKFLOW.id,
{
maxAgeInDays: 10,
maxRunsPerBranch: 1,
}
);

expect([...actual.entries()]).not.toHaveLength(0);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const runsOfBranchMain = actual.get('main')!;
expect(runsOfBranchMain).toBeDefined();
expect(runsOfBranchMain).not.toHaveLength(0);

const actualRun = runsOfBranchMain[0];
expect(actualRun.mainAuthor).toEqual<WorkflowRunAuthor>({
login: expect.stringContaining(''),
name: expect.stringContaining(''),
});
}, 20000);

test('should name branches according to triggering event', async () => {
const sut = new WorkflowRunRepository(octokitFactory);
const sut = new WorkflowRunRepository(octokitFactory, emptyCommitAutorRepo);

const actual = await sut.getLatestRunsForWorkflow(
testCredentials.PAT_NO_SCOPE,
Expand Down Expand Up @@ -74,7 +118,7 @@ describe('WorkflowRunRepository', () => {
});

test('should sort builds from older to newer', async () => {
const sut = new WorkflowRunRepository(octokitFactory);
const sut = new WorkflowRunRepository(octokitFactory, emptyCommitAutorRepo);

const actual = await sut.getLatestRunsForWorkflow(
testCredentials.PAT_NO_SCOPE,
Expand All @@ -97,7 +141,7 @@ describe('WorkflowRunRepository', () => {

test('should apply maxAgeInDays', async () => {
const maxAgeInDays = 3;
const sut = new WorkflowRunRepository(octokitFactory);
const sut = new WorkflowRunRepository(octokitFactory, emptyCommitAutorRepo);

const actual = await sut.getLatestRunsForWorkflow(
testCredentials.PAT_NO_SCOPE,
Expand All @@ -119,7 +163,7 @@ describe('WorkflowRunRepository', () => {

test('should apply maxRunsPerBranch in repo with lots of activity', async () => {
const maxRunsPerBranch = 2;
const sut = new WorkflowRunRepository(octokitFactory);
const sut = new WorkflowRunRepository(octokitFactory, emptyCommitAutorRepo);

const actual = await sut.getLatestRunsForWorkflow(
testCredentials.PAT_NO_SCOPE,
Expand Down

0 comments on commit a68f7ad

Please sign in to comment.