Skip to content

Commit

Permalink
Add Serverless release notes (#35)
Browse files Browse the repository at this point in the history
* add serverless template

* ignore resizeobserver error

* find last two prod commits

* add lodash chunk

* get all kibana prs between commits

* use basehead function

* fix types. use package ts

* create steps dynamically

* add serverless pr filtering

* fix output

* add missing fields to serverless pr

* fix loadPrs for serverless

* add error handling

* cleanup

* add date for serverless template

* get deploy tag for date

* use tag as serverless version output

* repo name

* clean up
  • Loading branch information
Ikuni17 authored Nov 18, 2024
1 parent 74af724 commit 778d7fd
Show file tree
Hide file tree
Showing 10 changed files with 592 additions and 243 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
50 changes: 41 additions & 9 deletions config-overrides.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
const webpack = require('webpack');

module.exports = (webpackConfig) => {
module.exports = {
webpack: (webpackConfig) => {
webpackConfig.resolve.fallback = {
...webpackConfig.resolve.fallback,
"url": require.resolve("url/"),
"querystring": require.resolve("querystring-es3")
...webpackConfig.resolve.fallback,
url: require.resolve('url/'),
querystring: require.resolve('querystring-es3'),
};

const basename = process.env.BASENAME;
if (basename) {
webpackConfig.output.publicPath = `/${basename}/`;
webpackConfig.output.publicPath = `/${basename}/`;

webpackConfig.plugins.push(new webpack.DefinePlugin({
_BASENAME_: `'${basename}'`
}));
webpackConfig.plugins.push(
new webpack.DefinePlugin({
_BASENAME_: `'${basename}'`,
})
);
}

return webpackConfig;
}
},
devServer: (configFunction) => {
return function (proxy, allowedHost) {
// Create the default config
const config = configFunction(proxy, allowedHost);

config.client = {
...config.client,
overlay: {
...config.client.overlay,
runtimeErrors: (error) => {
/**
* This error occurs every time a version is selected in the wizard,
* and causes the overlay to appear. It is stemming from a package.
*/
if (
error?.message === 'ResizeObserver loop completed with undelivered notifications.'
) {
console.error(error);
return false;
}
return true;
},
},
};

return config;
};
},
};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"@elastic/datemath": "^5.0.3",
"@elastic/eui": "^41.2.1",
"@monaco-editor/react": "^4.3.1",
"@octokit/graphql-schema": "13.7.0",
"@octokit/rest": "^18.12.0",
"@octokit/types": "^6.34.0",
"@testing-library/jest-dom": "^5.15.1",
Expand All @@ -18,6 +19,7 @@
"@types/react-dom": "^17.0.11",
"@xstate/react": "^1.6.3",
"asciidoctor": "^2.2.5",
"lodash.chunk": "^4.2.0",
"lodash.clonedeep": "^4.5.0",
"lodash.uniq": "^4.5.0",
"moment": "^2.29.1",
Expand Down Expand Up @@ -57,6 +59,7 @@
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@types/lodash.chunk": "^4.2.9",
"@types/lodash.clonedeep": "^4.5.6",
"@types/lodash.uniq": "^4.5.6",
"@types/mustache": "^4.1.2",
Expand Down
145 changes: 144 additions & 1 deletion src/common/github-service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { useMemo } from 'react';
import { Octokit } from '@octokit/rest';
import uniq from 'lodash.uniq';
import chunk from 'lodash.chunk';
import { Endpoints, RequestError } from '@octokit/types';
import { Commit, PullRequest, Label as GQLLabel } from '@octokit/graphql-schema';
import { GITHUB_OWNER } from './constants';
import { getOctokit } from './github';
import semver, { SemVer } from 'semver';
import parseLinkHeader from 'parse-link-header';
import { Observable, Subject } from 'rxjs';
import { useNavigate } from 'react-router-dom';
import { useActiveConfig } from '../config';
import { Config, useActiveConfig } from '../config';

type Progress<T> =
| { type: 'progress'; items: T[]; percentage: number }
| { type: 'complete'; items: T[] };

export type PrItem = Endpoints['GET /search/issues']['response']['data']['items'][number];
export type Label = PrItem['labels'][number];
export type ServerlessPrItem = Pick<
PullRequest,
'id' | 'url' | 'title' | 'number' | 'body' | 'labels' | 'author'
>;

interface GitHubServiceConfig {
octokit: Octokit;
Expand Down Expand Up @@ -45,6 +51,8 @@ class GitHubService {
private octokit: Octokit;
private repoId: number | undefined;
public repoName: string;
public serverlessReleaseDate: string | undefined;
public serverlessReleaseTag: string = '';

constructor(config: GitHubServiceConfig) {
this.octokit = config.octokit;
Expand Down Expand Up @@ -270,6 +278,141 @@ class GitHubService {

return progressSubject$.asObservable();
}

public async getPrsForServerless(config: Config) {
const { excludedLabels = [], includedLabels = [] } = config;

/**
* Find the last two Kibana commits which were promoted to production-canary successfully. We
* cannot use the deploy@ tags from the Kibana repo, since they do not always reach prod. We
* need to be careful matching with this query because kibana-controller is managed in serverless-gitops as well.
*/
const commits = await this.octokit.search
.commits({
q: `repo:${GITHUB_OWNER}/serverless-gitops "gitops: production-canary-ds" "Artifact promotion for kibana to git-"`,
sort: 'committer-date',
})
.catch((error) => {
throw error;
});

const shas = commits.data.items
.slice(0, 2)
.map((item) => item.commit.message.split('See elastic/kibana@')[1]);

// Need to retrieve all the tags because ref tags are always last
const tags = await this.octokit
.paginate(this.octokit.repos.listTags, {
owner: GITHUB_OWNER,
repo: 'kibana',
per_page: 100,
})
.catch((error) => {
throw error;
});

const tagForReleaseCommit = tags.filter((tag) => tag.commit.sha.startsWith(shas[0])).pop();

if (tagForReleaseCommit) {
this.serverlessReleaseTag = tagForReleaseCommit.name;
this.serverlessReleaseDate = new Date(
Number(tagForReleaseCommit.name.split('@')[1]) * 1000
).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
} else {
throw new Error('No tag found for the release commit');
}

// Get all the merge commit between the two releases
const compareResult = await this.octokit.repos
.compareCommitsWithBasehead({
owner: GITHUB_OWNER,
repo: 'kibana',
basehead: `${shas[1]}...${shas[0]}`,
})
.catch((error) => {
throw error;
});

// Find all the PRs which were associated with the merge commits
const commitNodeIds = compareResult.data.commits.map((commit) => commit.node_id);
const query = `
query($commitNodeIds: [ID!]!) {
nodes(ids: $commitNodeIds) {
... on Commit {
associatedPullRequests(first: 1) {
nodes {
id
url
title
number
body
author {
login
}
labels(first: 50) {
nodes {
name
}
}
}
}
}
}
}
`;

const pullRequests: ServerlessPrItem[] = [];
// Can use chunks up to 100, but they slow down the requests significantly
const chunks = chunk(commitNodeIds, 20);

const promises = chunks.map((chunk) => {
const variables = {
commitNodeIds: chunk,
};

return this.octokit.graphql<{ nodes: Commit[] }>(query, variables);
});

const results = await Promise.all(promises).catch((error) => {
throw error;
});

results.forEach((result) => {
result.nodes.forEach((node) => {
if (node.associatedPullRequests) {
node.associatedPullRequests.nodes?.forEach((pr) => {
if (pr?.labels?.nodes) {
// Cannot filter by label in the GraphQL query, so we need to do it here
const prLabels = pr.labels.nodes.map((label) => (label as GQLLabel).name);
const hasExcludedLabel = prLabels.some((label) => excludedLabels.includes(label));
const hasIncludedLabel =
includedLabels.length === 0 ||
prLabels.some((label) => includedLabels.includes(label));

if (!hasExcludedLabel && hasIncludedLabel) {
pullRequests.push(pr);
}
}
});
}
});
});

return pullRequests.map((pr) => {
return {
...pr,
labels: pr.labels?.nodes ?? [],
user: pr.author,
html_url: pr.url,
};
// Coerce to any because there is not full overlap between ServerlessPrItem and PrItem
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any as PrItem[];
}
}

let service: GitHubService | undefined;
Expand Down
4 changes: 3 additions & 1 deletion src/config/templates/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { kibanaTemplate } from './kibana';
import { securityTemplate } from './security';
import { endpointTemplate } from './endpoint';
import { serverlessTemplate } from './serverless';
import { Config } from './types';
import type { EuiIconProps } from '@elastic/eui';

export * from './types';

export type TemplateId = 'kibana' | 'security' | 'endpoint';
export type TemplateId = 'kibana' | 'security' | 'endpoint' | 'serverless';

export interface TemplateInfo {
id: TemplateId;
Expand All @@ -19,4 +20,5 @@ export const templates: TemplateInfo[] = [
{ id: 'kibana', name: 'Kibana', icon: 'logoKibana', config: kibanaTemplate },
{ id: 'security', name: 'Security', icon: 'logoSecurity', config: securityTemplate },
{ id: 'endpoint', name: 'Endpoint', icon: 'logoElastic', config: endpointTemplate },
{ id: 'serverless', name: 'Serverless', icon: 'logoKibana', config: serverlessTemplate },
];
72 changes: 72 additions & 0 deletions src/config/templates/serverless.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { Config } from './types';

export const serverlessLabels = [
'Team:SecuritySolution',
'Team: SecuritySolution',
'serverless-bugfix',
'serverless-enhancement',
];

export const serverlessTemplate: Config = {
repoName: 'kibana',
includedLabels: serverlessLabels,
excludedLabels: ['backport', 'release_note:skip', 'reverted'],
areas: [
{
title: 'Elastic Security',
labels: serverlessLabels,
},
],
templates: {
pages: {
releaseNotes: `[discrete]
[[release-notes-{{version}}]]
=== {{serverlessReleaseDate}}
{{#prs.breaking}}
[discrete]
[[breaking-changes-{{version}}]]
==== Breaking changes
{{{prs.breaking}}}
{{/prs.breaking}}
{{#prs.deprecations}}
[discrete]
[[deprecations-{{version}}]]
==== Deprecations
{{{prs.deprecations}}}
{{/prs.deprecations}}
{{#prs.features}}
[discrete]
[[features-{{version}}]]
==== New features
{{{prs.features}}}
{{/prs.features}}
{{#prs.enhancements}}
[discrete]
[[enhancements-{{version}}]]
==== Enhancements
{{{prs.enhancements}}}
{{/prs.enhancements}}
{{#prs.fixes}}
[discrete]
[[bug-fixes-{{version}}]]
==== Bug fixes
{{{prs.fixes}}}
{{/prs.fixes}}
`,
},
prGroup: '{{{prs}}}',
prs: {
breaking: `*{{{title}}}*\n\n!!TODO!!\n\nSee ({kibana-pull}{{number}}[#{{number}}]) for details.\n`,
deprecation: `*{{{title}}}*\n\n!!TODO!!\n\nSee ({kibana-pull}{{number}}[#{{number}}]) for details.\n`,
_other_:
'* {{{title}}} ({kibana-pull}{{number}}[#{{number}}]).' +
'{{#details}}\n////\n!!TODO!! The above PR had a lengthy release note description:\n{{{details}}}\n////{{/details}}',
},
},
};
Loading

0 comments on commit 778d7fd

Please sign in to comment.