Skip to content

Commit

Permalink
Add link to download crash reports
Browse files Browse the repository at this point in the history
This is useful for the new .ips format crash reports being uploaded as
of ably/ably-cocoa#1688, which are not very
human-readable, but which macOS is capable of nicely formatting.
  • Loading branch information
lawrence-forooghian committed Apr 25, 2023
1 parent 3a0e50d commit 97e0acf
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { TestCasesModule } from './testCases/testCases.module';
import { FailuresModule } from './failures/failures.module';
import { ReposModule } from './repos/repos.module';
import { CrashReport } from './uploads/crashReport.entity';
import { CrashReportsModule } from './crashReports/crashReports.module';

@Module({
imports: [
Expand All @@ -31,6 +32,7 @@ import { CrashReport } from './uploads/crashReport.entity';
TestCasesModule,
FailuresModule,
ReposModule,
CrashReportsModule,
],
controllers: [AppController],
})
Expand Down
29 changes: 29 additions & 0 deletions src/crashReports/crashReports.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Controller, Get, Param, Res, Header } from '@nestjs/common';
import { CrashReportsService } from './crashReports.service';
import { Response } from 'express';

@Controller('repos/:owner/:name/crash_reports')
export class CrashReportsController {
constructor(private readonly crashReportsService: CrashReportsService) {}

@Get(':id/download')
@Header('Content-Type', 'application/octet-stream')
async download(
@Param('id') id: string,
@Res({ passthrough: true }) res: Response,
): Promise<string> {
const crashReport = await this.crashReportsService.find(id);

// Only repeat the extension of the original file if it’s one of our expected ones. Not really sure if this is of any use, but I don’t really like the idea of being tricked into giving somebody a malicious .dmg file or something.
const allowedExtensions = ['.crash', '.ips'];
const extensionToUse = allowedExtensions.find((allowedExtension) =>
crashReport.filename.endsWith(allowedExtension),
);

res.header(
'Content-Disposition',
`filename="crash_report_${crashReport.id}${extensionToUse ?? ''}"`,
);
return crashReport.data;
}
}
12 changes: 12 additions & 0 deletions src/crashReports/crashReports.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CrashReport } from '../uploads/crashReport.entity';
import { CrashReportsController } from './crashReports.controller';
import { CrashReportsService } from './crashReports.service';

@Module({
imports: [TypeOrmModule.forFeature([CrashReport])],
controllers: [CrashReportsController],
providers: [CrashReportsService],
})
export class CrashReportsModule {}
19 changes: 19 additions & 0 deletions src/crashReports/crashReports.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CrashReport } from '../uploads/crashReport.entity';

@Injectable()
export class CrashReportsService {
constructor(
@InjectRepository(CrashReport)
private crashReportsRepository: Repository<CrashReport>,
) {}

async find(id: string): Promise<CrashReport> {
const results = await this.crashReportsRepository.find({
where: { id },
});
return results[0];
}
}
5 changes: 5 additions & 0 deletions src/failures/failure.viewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface CrashReportViewModel {
id: string;
metadataDescriptionList: DescriptionListViewModel;
report: string;
downloadHref: string;
}

export class FailureViewModel {
Expand Down Expand Up @@ -67,6 +68,10 @@ export class FailureViewModel {
],
},
report: crashReport.data,
downloadHref: ViewModelURLHelpers.hrefForCrashReportDownload(
crashReport.id,
this.repo,
),
}),
);
}
6 changes: 6 additions & 0 deletions src/utils/viewModel/urlHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ export class ViewModelURLHelpers {
return `/repos/${repoSlug(repo)}/failures/${encodeURIComponent(id)}`;
}

static hrefForCrashReportDownload(id: string, repo: Repo) {
return `/repos/${repoSlug(repo)}/crash_reports/${encodeURIComponent(
id,
)}/download`;
}

static queryComponentsForFilter(
filter: UploadsFilter | null,
{ paramPrefix }: { paramPrefix: string | null } = { paramPrefix: null },
Expand Down
2 changes: 1 addition & 1 deletion views/failures/details.njk
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

{% if viewModel.crashReports | length %}
{% for crashReport in viewModel.crashReports %}
<h3>{{ crashReport.id }}</h3>
<h3>{{ crashReport.id }} (<a href="{{ crashReport.downloadHref }}">Download</a>)</h3>

{{ descriptionList(crashReport.metadataDescriptionList) }}

Expand Down

0 comments on commit 97e0acf

Please sign in to comment.