Skip to content

Commit

Permalink
Merge pull request #72 from ably/add-link-to-download-crash-report
Browse files Browse the repository at this point in the history
Add link to download crash report
  • Loading branch information
lawrence-forooghian committed Apr 25, 2023
2 parents 165c3b4 + 97e0acf commit 37c28e8
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 3 deletions.
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];
}
}
22 changes: 20 additions & 2 deletions src/failures/failure.viewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { UploadsFilter } from 'src/uploads/uploads.service';
import { Repo } from 'src/repos/repo';

interface CrashReportViewModel {
title: string;
id: string;
metadataDescriptionList: DescriptionListViewModel;
report: string;
downloadHref: string;
}

export class FailureViewModel {
Expand Down Expand Up @@ -52,8 +54,24 @@ export class FailureViewModel {

readonly crashReports: CrashReportViewModel[] = this.failure.crashReports.map(
(crashReport) => ({
title: crashReport.filename,
id: crashReport.id,
originalFilename: crashReport.filename,
metadataDescriptionList: {
items: [
{
term: 'Original filename',
description: {
type: 'text',
text: crashReport.filename,
},
},
],
},
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
4 changes: 3 additions & 1 deletion views/failures/details.njk
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

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

{{ descriptionList(crashReport.metadataDescriptionList) }}

<pre>{{crashReport.report}}</pre>
{% endfor %}
Expand Down

0 comments on commit 37c28e8

Please sign in to comment.