Skip to content

Commit

Permalink
Added fetching of labels marked as incidents (#9)
Browse files Browse the repository at this point in the history
Added the ability to fetch labels on the cron job and add them to a new
array named `incidents`.

Restructured the report file (and renamed the file to stop it from
causing problems).

Updated the interface to show the new incidents using a timeline.

Prettified the footer

## Summary

- **New Features**
	- Introduced `IncidentManager` class for managing GitHub incidents.
- Added a new Svelte component to display recent incidents in a timeline
format.
	- Enhanced page functionality with conditional rendering of incidents.
- Expanded data retrieval capabilities with the addition of incident
loading functions.

- **Improvements**
	- Updated artifact and incident handling to streamline processes.
	- Enhanced layout of the component footer for better user experience.
	- Improved the data structure for incident tracking and reporting.

- **Bug Fixes**
- Refined artifact management methods for better performance and
accuracy.
	- Modified test case structures to accommodate new data formats.
  • Loading branch information
Bullrich authored Aug 6, 2024
1 parent 60344f3 commit aa10eb1
Show file tree
Hide file tree
Showing 14 changed files with 767 additions and 637 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/deploy-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
- cron: '0 */6 * * *'

env:
ARTIFACT_NAME: report
ARTIFACT_NAME: reports

jobs:
get-metrics:
Expand All @@ -21,6 +21,7 @@ jobs:
contents: read
actions: read
packages: write
issues: read
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
4 changes: 2 additions & 2 deletions log/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
.idea

# Report files
report.json
report.zip
reports.json
reports.zip
log/*.json

.env
8 changes: 4 additions & 4 deletions log/src/github/artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class ArtifactManager {
* Search on the previous GitHub runs for the artifacts and it tries to find a matching one.
* If it does, it downloads it and parses the content. If not, it returns null
*/
async getPreviousArtifact(repo: Repo, workflowName: string): Promise<Array<ReportFile> | null> {
async getPreviousArtifact(repo: Repo, workflowName: string): Promise<ReportFile | null> {
this.logger.info(`Looking for previous artifact for workflow: '${workflowName}'`);
const workflows = await this.api.rest.actions.listRepoWorkflows(repo);

Expand Down Expand Up @@ -78,8 +78,8 @@ export class ArtifactManager {
const fileContent = await readFile(artifactLocation, "utf-8");
this.logger.debug(`Old artifact: ${fileContent}`);
try {
const parsedFile: ReportFile[] = JSON.parse(fileContent);
if (parsedFile.length > 0) {
const parsedFile: ReportFile = JSON.parse(fileContent);
if (parsedFile.site.length > 0) {
return parsedFile;
}
} catch (err) {
Expand All @@ -96,7 +96,7 @@ export class ArtifactManager {
* @param reports the reports to be stringified
* @returns The location of the file
*/
async generateArtifact(reports: Array<ReportFile>): Promise<string> {
async generateArtifact(reports: ReportFile): Promise<string> {
const reportContent = JSON.stringify(reports);
const location = resolve(`${this.artifactName}.json`);
this.logger.debug(`Writing to ${location} the content of file ${reportContent}`);
Expand Down
40 changes: 40 additions & 0 deletions log/src/github/incidents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import moment from "moment";
import { ReportFile } from "../types";
import { ActionLogger, GitHubClient, Repo } from "./types";

export class IncidentManager {
constructor(
private readonly api: GitHubClient,
private readonly logger: ActionLogger,
private readonly labelName: string = "incident",

) { }

async obtainPastIncidents(repo: Repo, maxAge: number): Promise<ReportFile["incidents"]> {
this.logger.info("Searching for previous incidents")
const issues = await this.api.rest.issues.listForRepo({ ...repo });
this.logger.info(`Found ${issues.data.length} issues`);
const incidents: ReportFile["incidents"] = []

for (const issue of issues.data) {
if (issue.labels.some(l => {
if (typeof l === "string") {
return l.toLocaleUpperCase() === this.labelName.toLocaleUpperCase();
} else {
return l.name?.toLocaleUpperCase() === this.labelName.toLocaleUpperCase();
}
})) {
const creationDate = moment(issue.created_at);
if (Math.abs(creationDate.diff(moment.now(), "days")) < maxAge) {
incidents.push({ date: creationDate.unix(), title: issue.title, open: issue.state === "open" });
} else {
this.logger.info(`Issue ${issue.title} is older than ${maxAge} days`);
}
}
}

this.logger.info(`${incidents.length} of this issues were incidents`);

return incidents;
}
}
34 changes: 23 additions & 11 deletions log/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Repo } from "./github/types";
import { StatusChecker } from "./status";
import { ReportFile } from "./types";
import { generateCoreLogger } from "./util";
import { IncidentManager } from "./github/incidents";

export const env = envsafe({
SOURCES: str(),
Expand All @@ -21,6 +22,10 @@ export const env = envsafe({
AGE_LIMIT: num({
default: 45,
desc: "Limit age (in days) of how old can the timestamps be"
}),
INCIDENT_LABEL: str({
default: "incident",
desc: "Name of the label. Not case sensitive"
})
});

Expand Down Expand Up @@ -50,16 +55,17 @@ const run = async () => {
const token = env.GITHUB_TOKEN;
const api = getOctokit(token);
const artifactManager = new ArtifactManager(api, logger, env.ARTIFACT_NAME);
const incidentManager = new IncidentManager(api, logger, env.INCIDENT_LABEL);

const artifact = await artifactManager.getPreviousArtifact(repo, env.JOB_NAME);
logger.info(`Found artifact with ${artifact?.length} elements`);
logger.info(`Found artifact with ${artifact?.site.length ?? 0} elements`);

const siteResult: Map<string, ReportFile> = new Map();
const siteResult: Map<string, ReportFile["site"][number]["status"]> = new Map();

if (artifact) {
logger.info(`Mapping old report`);
artifact.forEach(report => {
siteResult.set(report.name, report)
artifact.site.forEach(report => {
siteResult.set(report.name, report.status)
});
}

Expand All @@ -70,22 +76,24 @@ const run = async () => {
const statusChecker = new StatusChecker(name, url, logger);
const result = await statusChecker.verifyEndpoint();

let report = siteResult.get(name);
let report: ReportFile["site"][number]["status"] | undefined = siteResult.get(name);

// Create report if it doesn't exist
if (!report) {
report = { name, status: [] };
report = [];
}

// We push the value to the status array
report.status.push({ timestamp: now, result });
report.push({ timestamp: now, result });
siteResult.set(name, report);
}

const siteReports: ReportFile["site"] = [];

for (const [name, report] of siteResult) {
const beforeCleanLength = report.status.length;
const beforeCleanLength = report.length;
// Clean old timestamp reports
const cleanedStatus = report.status.filter(({ timestamp }) => Math.abs(moment.unix(timestamp).diff(moment.now(), "days")) < env.AGE_LIMIT);
const cleanedStatus = report.filter(({ timestamp }) => Math.abs(moment.unix(timestamp).diff(moment.now(), "days")) < env.AGE_LIMIT);

if (cleanedStatus.length !== beforeCleanLength) {
logger.debug(`Removed ${beforeCleanLength - cleanedStatus.length} elements from '${name}'`);
Expand All @@ -97,11 +105,15 @@ const run = async () => {
logger.info(`Deleted report for '${name}' because it's empty`)
} else {
logger.info(`'${name}' status has ${cleanedStatus.length} status`);
siteResult.set(name, { name, status: cleanedStatus });
siteReports.push({ name, status: cleanedStatus });
}
}

const file = await artifactManager.generateArtifact(Array.from(siteResult.values()));
const incidents = await incidentManager.obtainPastIncidents(repo, env.AGE_LIMIT);

const report: ReportFile = { incidents, site: siteReports }

const file = await artifactManager.generateArtifact(report);
setOutput("file", file);
}

Expand Down
4 changes: 2 additions & 2 deletions log/src/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ for (const [site, url] of sites) {
}

test("Write to doc", async () => {
const siteResult: Array<ReportFile> = [];
const siteResult: ReportFile["site"] = [];

for (const [site, url] of sites) {
const statusChecker = new StatusChecker(site, url, console);
siteResult.push({ name: site, status: [{ timestamp: new Date().getTime(), result: (await statusChecker.verifyEndpoint()) }] });
}
const am = new ArtifactManager(null as unknown as GitHubClient, console, "test");
await am.generateArtifact(siteResult);
await am.generateArtifact({ incidents: [], site: siteResult });
})
34 changes: 34 additions & 0 deletions src/lib/components/Incidents.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script lang="ts">
import type { ReportFile } from '$lib/types';
import moment from 'moment';
export let incidents: ReportFile['incidents'];
</script>

<div class="">
<h1 class="text-2xl font-bold text-left">Recent incidents</h1>
<ul class="timeline timeline-vertical mt-4 lg:mt10">
{#each incidents as incident, i}
<li>
{#if i > 0}
<hr class:bg-primary={incident.open} />
{/if}
<div class="timeline-start">{moment.unix(incident.date).format('MMM Do YY')}</div>
<div class="timeline-middle" class:text-primary={incident.open}>
<div class="badge" class:badge-primary={incident.open}>
{#if incident.open}
{:else}
{/if}
</div>
</div>
<div class="timeline-end timeline-box">{incident.title}</div>

{#if i < incidents.length - 1}
<hr class:bg-primary={incident.open} />
{/if}
</li>
{/each}
</ul>
</div>
Loading

0 comments on commit aa10eb1

Please sign in to comment.