Skip to content

Commit

Permalink
feat: add reviewers blocks to generators.yml (#3952)
Browse files Browse the repository at this point in the history
  • Loading branch information
armandobelardo authored Jun 30, 2024
1 parent dc2352d commit 980b5b5
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { Values } from "@fern-api/core-utils";
import { AbsoluteFilePath } from "@fern-api/fs-utils";
import { FernFiddle } from "@fern-fern/fiddle-sdk";
import { Audiences } from "../commons";
import { APIDefinitionSettingsSchema } from "./schemas/APIConfigurationSchema";
import { GeneratorsConfigurationSchema } from "./schemas/GeneratorsConfigurationSchema";
import { ReadmeSchema } from "./schemas/ReadmeSchema";
import { APIDefinitionSettingsSchema } from "./schemas/APIConfigurationSchema";

export interface GeneratorsConfiguration {
api?: APIDefinition;
defaultGroup: string | undefined;
reviewers: Reviewers | undefined;
groups: GeneratorGroup[];
whitelabel: FernFiddle.WhitelabelConfig | undefined;

Expand Down Expand Up @@ -40,13 +41,25 @@ export interface GeneratorGroup {
groupName: string;
audiences: Audiences;
generators: GeneratorInvocation[];
reviewers: Reviewers | undefined;
}

export interface Reviewer {
name: string;
}

export interface Reviewers {
teams?: Reviewer[] | undefined;
users?: Reviewer[] | undefined;
}

export interface GeneratorInvocation {
name: string;
irVersionOverride: string | undefined;
version: string;
config: unknown;
// Note this also includes a reviewers block for PR mode, it's from fiddle
// and the same schema
outputMode: FernFiddle.remoteGen.OutputMode;
absolutePathToLocalOutput: AbsoluteFilePath | undefined;
absolutePathToLocalSnippets: AbsoluteFilePath | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,50 @@ describe("convertGeneratorsConfiguration", () => {
}
});

it("Reviewers", async () => {
const converted = await convertGeneratorsConfiguration({
absolutePathToGeneratorsConfiguration: AbsoluteFilePath.of(__filename),
rawGeneratorsConfiguration: {
reviewers: {
teams: [{ name: "fern-eng" }],
users: [{ name: "armando" }]
},
groups: {
"stage:java": {
generators: [
{
name: "fernapi/fern-java-sdk",
version: "0.8.8-rc0",
config: {
"package-prefix": "com.test.sdk"
},
metadata: {
license: "MIT"
},
github: {
repository: "fern-api/github-app-test",
mode: "pull-request",
reviewers: {
users: [{ name: "deep" }]
}
}
}
]
}
}
}
});
const output = converted.groups[0]?.generators[0]?.outputMode;
expect(output?.type).toEqual("githubV2");
if (output?.type === "githubV2" && output.githubV2.type === "pullRequest") {
expect(output.githubV2.reviewers != null).toBeTruthy();
expect(output.githubV2.reviewers?.length).toEqual(3);

const reviewerNames = output.githubV2.reviewers?.map((reviewer) => reviewer.name);
expect(reviewerNames).toEqual(["fern-eng", "armando", "deep"]);
}
});

it("Output Metadata", async () => {
const converted = await convertGeneratorsConfiguration({
absolutePathToGeneratorsConfiguration: AbsoluteFilePath.of(__filename),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { assertNever } from "@fern-api/core-utils";
import { AbsoluteFilePath, dirname, join, RelativeFilePath, resolve } from "@fern-api/fs-utils";
import { FernFiddle } from "@fern-fern/fiddle-sdk";
import { OutputMetadata, PublishingMetadata, PypiMetadata } from "@fern-fern/fiddle-sdk/api";
import { GithubPullRequestReviewer, OutputMetadata, PublishingMetadata, PypiMetadata } from "@fern-fern/fiddle-sdk/api";
import { readFile } from "fs/promises";
import path from "path";
import {
Expand All @@ -24,10 +24,12 @@ import {
OPENAPI_OVERRIDES_LOCATION_KEY
} from "./schemas/GeneratorsConfigurationSchema";
import { GithubLicenseSchema } from "./schemas/GithubLicenseSchema";
import { GithubPullRequestSchema } from "./schemas/GithubPullRequestSchema";
import { MavenOutputLocationSchema } from "./schemas/MavenOutputLocationSchema";
import { OutputMetadataSchema } from "./schemas/OutputMetadataSchema";
import { PypiOutputMetadataSchema } from "./schemas/PypiOutputMetadataSchema";
import { ReadmeSchema } from "./schemas/ReadmeSchema";
import { ReviewersSchema } from "./schemas/ReviewersSchema";

export async function convertGeneratorsConfiguration({
absolutePathToGeneratorsConfiguration,
Expand All @@ -44,6 +46,7 @@ export async function convertGeneratorsConfiguration({
api: parsedApiConfiguration,
rawConfiguration: rawGeneratorsConfiguration,
defaultGroup: rawGeneratorsConfiguration["default-group"],
reviewers: rawGeneratorsConfiguration.reviewers,
groups:
rawGeneratorsConfiguration.groups != null
? await Promise.all(
Expand All @@ -53,6 +56,7 @@ export async function convertGeneratorsConfiguration({
groupName,
group,
maybeTopLevelMetadata,
maybeTopLevelReviewers: rawGeneratorsConfiguration.reviewers,
readme
})
)
Expand Down Expand Up @@ -175,17 +179,20 @@ async function convertGroup({
groupName,
group,
maybeTopLevelMetadata,
maybeTopLevelReviewers,
readme
}: {
absolutePathToGeneratorsConfiguration: AbsoluteFilePath;
groupName: string;
group: GeneratorGroupSchema;
maybeTopLevelMetadata: OutputMetadata | undefined;
maybeTopLevelReviewers: ReviewersSchema | undefined;
readme: ReadmeSchema | undefined;
}): Promise<GeneratorGroup> {
const maybeGroupLevelMetadata = getOutputMetadata(group.metadata);
return {
groupName,
reviewers: group.reviewers,
audiences: group.audiences == null ? { type: "all" } : { type: "select", audiences: group.audiences },
generators: await Promise.all(
group.generators.map((generator) =>
Expand All @@ -194,6 +201,8 @@ async function convertGroup({
generator,
maybeTopLevelMetadata,
maybeGroupLevelMetadata,
maybeTopLevelReviewers,
maybeGroupLevelReviewers: group.reviewers,
readme
})
)
Expand All @@ -206,12 +215,16 @@ async function convertGenerator({
generator,
maybeGroupLevelMetadata,
maybeTopLevelMetadata,
maybeGroupLevelReviewers,
maybeTopLevelReviewers,
readme
}: {
absolutePathToGeneratorsConfiguration: AbsoluteFilePath;
generator: GeneratorInvocationSchema;
maybeGroupLevelMetadata: OutputMetadata | undefined;
maybeTopLevelMetadata: OutputMetadata | undefined;
maybeGroupLevelReviewers: ReviewersSchema | undefined;
maybeTopLevelReviewers: ReviewersSchema | undefined;
readme: ReadmeSchema | undefined;
}): Promise<GeneratorInvocation> {
return {
Expand All @@ -222,7 +235,9 @@ async function convertGenerator({
absolutePathToGeneratorsConfiguration,
generator,
maybeGroupLevelMetadata,
maybeTopLevelMetadata
maybeTopLevelMetadata,
maybeGroupLevelReviewers,
maybeTopLevelReviewers
}),
keywords: generator.keywords,
smartCasing: generator["smart-casing"] ?? false,
Expand Down Expand Up @@ -283,16 +298,63 @@ function _getPypiMetadata({
}
return maybePyPiMetadata;
}

function _getReviewers({
topLevelReviewers,
groupLevelReviewers,
outputModeReviewers
}: {
topLevelReviewers: ReviewersSchema | undefined;
groupLevelReviewers: ReviewersSchema | undefined;
outputModeReviewers: ReviewersSchema | undefined;
}): GithubPullRequestReviewer[] {
const teamNames = new Set<string>();
const userNames = new Set<string>();

const reviewers: GithubPullRequestReviewer[] = [];

const allTeamReviewers = [
...(topLevelReviewers?.teams ?? []),
...(groupLevelReviewers?.teams ?? []),
...(outputModeReviewers?.teams ?? [])
];
const allUserReviewers = [
...(topLevelReviewers?.users ?? []),
...(groupLevelReviewers?.users ?? []),
...(outputModeReviewers?.users ?? [])
];

for (const team of allTeamReviewers) {
if (!teamNames.has(team.name)) {
reviewers.push(GithubPullRequestReviewer.team({ name: team.name }));
teamNames.add(team.name);
}
}

for (const user of allUserReviewers) {
if (!userNames.has(user.name)) {
reviewers.push(GithubPullRequestReviewer.user({ name: user.name }));
userNames.add(user.name);
}
}

return reviewers;
}

async function convertOutputMode({
absolutePathToGeneratorsConfiguration,
generator,
maybeGroupLevelMetadata = {},
maybeTopLevelMetadata = {}
maybeTopLevelMetadata = {},
maybeGroupLevelReviewers,
maybeTopLevelReviewers
}: {
absolutePathToGeneratorsConfiguration: AbsoluteFilePath;
generator: GeneratorInvocationSchema;
maybeGroupLevelMetadata: OutputMetadata | undefined;
maybeTopLevelMetadata: OutputMetadata | undefined;
maybeGroupLevelReviewers: ReviewersSchema | undefined;
maybeTopLevelReviewers: ReviewersSchema | undefined;
}): Promise<FernFiddle.OutputMode> {
const downloadSnippets = generator.snippets != null && generator.snippets.path !== "";
if (generator.github != null) {
Expand Down Expand Up @@ -324,16 +386,23 @@ async function convertOutputMode({
downloadSnippets
})
);
case "pull-request":
case "pull-request": {
const reviewers = _getReviewers({
topLevelReviewers: maybeTopLevelReviewers,
groupLevelReviewers: maybeGroupLevelReviewers,
outputModeReviewers: (generator.github as GithubPullRequestSchema).reviewers
});
return FernFiddle.OutputMode.githubV2(
FernFiddle.GithubOutputModeV2.pullRequest({
owner,
repo,
license,
publishInfo,
downloadSnippets
downloadSnippets,
reviewers
})
);
}
case "push":
return FernFiddle.OutputMode.githubV2(
FernFiddle.GithubOutputModeV2.push({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { z } from "zod";
import { GeneratorInvocationSchema } from "./GeneratorInvocationSchema";
import { OutputMetadataSchema } from "./OutputMetadataSchema";
import { ReviewersSchema, REVIEWERS_KEY } from "./ReviewersSchema";

export const GeneratorGroupSchema = z.strictObject({
audiences: z.optional(z.array(z.string())),
generators: z.array(GeneratorInvocationSchema),
metadata: z.optional(OutputMetadataSchema)
metadata: z.optional(OutputMetadataSchema),
[REVIEWERS_KEY]: z.optional(ReviewersSchema)
});

export type GeneratorGroupSchema = z.infer<typeof GeneratorGroupSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GeneratorGroupSchema } from "./GeneratorGroupSchema";
import { GeneratorsOpenAPISchema } from "./GeneratorsOpenAPISchema";
import { OutputMetadataSchema } from "./OutputMetadataSchema";
import { ReadmeSchema } from "./ReadmeSchema";
import { ReviewersSchema, REVIEWERS_KEY } from "./ReviewersSchema";
import { WhitelabelConfigurationSchema } from "./WhitelabelConfigurationSchema";

export const DEFAULT_GROUP_GENERATORS_CONFIG_KEY = "default-group";
Expand All @@ -25,6 +26,8 @@ export const GeneratorsConfigurationSchema = z.strictObject({
[DEFAULT_GROUP_GENERATORS_CONFIG_KEY]: z.optional(z.string()),
groups: z.optional(z.record(GeneratorGroupSchema)),

[REVIEWERS_KEY]: z.optional(ReviewersSchema),

// deprecated, use the `api` key instead
[OPENAPI_LOCATION_KEY]: z.optional(GeneratorsOpenAPISchema),
[OPENAPI_OVERRIDES_LOCATION_KEY]: z.optional(z.string()),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { z } from "zod";
import { GithubLicenseSchema } from "./GithubLicenseSchema";
import { ReviewersSchema, REVIEWERS_KEY } from "./ReviewersSchema";

export const GithubPullRequestSchema = z.strictObject({
repository: z.string(),
branch: z.optional(z.string()),
license: z.optional(GithubLicenseSchema),
mode: z.literal("pull-request")
mode: z.literal("pull-request"),
[REVIEWERS_KEY]: z.optional(ReviewersSchema)
});

export type GithubPullRequestSchema = z.infer<typeof GithubPullRequestSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { z } from "zod";

export const REVIEWERS_KEY = "reviewers";

// Overkill object right now, but at some point we might
// need to specify orgs, or other things
const ReviewerSchema = z.strictObject({
name: z.string()
});

export const ReviewersSchema = z.strictObject({
teams: z.optional(z.array(ReviewerSchema)),
users: z.optional(z.array(ReviewerSchema))
});

export type ReviewersSchema = z.infer<typeof ReviewersSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class DockerTestRunner extends TestRunner {
}: TestRunner.DoRunArgs): Promise<void> {
const generatorGroup: generatorsYml.GeneratorGroup = {
groupName: "test",
reviewers: undefined,
audiences: selectAudiences != null ? { type: "select", audiences: selectAudiences } : ALL_AUDIENCES,
generators: [
getGeneratorInvocation({
Expand Down

0 comments on commit 980b5b5

Please sign in to comment.