Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/components/crate-sidebar.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export default class CrateSidebar extends Component {
</div>
{{/unless}}

{{#if (or this.showHomepage @version.documentationLink @crate.repository)}}
{{#if (or this.showHomepage @version.documentationLink @version.sourceLink @crate.repository)}}
<div class='links'>
{{#if this.showHomepage}}
<Link @title='Homepage' @url={{@crate.homepage}} data-test-homepage-link />
Expand All @@ -172,6 +172,10 @@ export default class CrateSidebar extends Component {
<Link @title='Documentation' @url={{@version.documentationLink}} data-test-docs-link />
{{/if}}

{{#if @version.sourceLink}}
<Link @title='Browse source' @url={{@version.sourceLink}} data-test-source-link />
{{/if}}

{{#if @crate.repository}}
<Link @title='Repository' @url={{@crate.repository}} data-test-repository-link />
{{/if}}
Expand Down
16 changes: 16 additions & 0 deletions app/models/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,22 @@ export default class Version extends Model {
return null;
}

get docsRsSourceLink() {
if (this.hasDocsRsLink) {
return `https://docs.rs/crate/${this.crateName}/${this.num}/source/`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can show the link more often.

from what I see, hasDocsRsLink is based on our status-json result (doc_status in there).

But we also have & host the source when:

  • it's a binary crate, or
  • the if the doc-build failed
  • when the build is in progress

The only edge cases here would be when the build is queued, or we didn't get the update from the registry yet.

I think you're good in just checking if you have a result from our status endpoint, no matter what you see in doc_status

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is also rust-lang/docs.rs#2762 where we would try to serve a nice page even for just queued releases

}
}

get sourceLink() {
// if we know about a successful docs.rs build, we'll return a link to that
let { docsRsSourceLink } = this;
if (docsRsSourceLink) {
return docsRsSourceLink;
}

return null;
}

yankTask = keepLatestTask(async () => {
let data = { version: { yanked: true } };
let payload = await waitForPromise(apiAction(this, { method: 'PATCH', data }));
Expand Down
98 changes: 98 additions & 0 deletions e2e/routes/crate/version/source-link.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { expect, test } from '@/e2e/helper';
import { http, HttpResponse } from 'msw';

test.describe('Route | crate.version | source link', { tag: '@routes' }, () => {
test('show docs.rs source link even if non-docs.rs documentation link is specified', async ({ page, msw }) => {
let crate = await msw.db.crate.create({ name: 'foo', documentation: 'https://foo.io/docs' });
await msw.db.version.create({ crate, num: '1.0.0' });

await page.goto('/crates/foo');
await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source/');

Check failure on line 10 in e2e/routes/crate/version/source-link.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend / Test (playwright)

[chromium] › e2e/routes/crate/version/source-link.spec.ts:5:7 › Route | crate.version | source link › show docs.rs source link even if non-docs.rs documentation link is specified @routes

1) [chromium] › e2e/routes/crate/version/source-link.spec.ts:5:7 › Route | crate.version | source link › show docs.rs source link even if non-docs.rs documentation link is specified @routes Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toHaveAttribute(expected) failed Locator: locator('[data-test-source-link] a') Expected: "https://docs.rs/crate/foo/1.0.0/source/" Timeout: 5000ms Error: element(s) not found Call log: - Expect "toHaveAttribute" with timeout 5000ms - waiting for locator('[data-test-source-link] a') 8 | 9 | await page.goto('/crates/foo'); > 10 | await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source/'); | ^ 11 | }); 12 | 13 | test('show no source link if there are no related docs.rs builds', async ({ at /home/runner/work/crates.io/crates.io/e2e/routes/crate/version/source-link.spec.ts:10:61

Check failure on line 10 in e2e/routes/crate/version/source-link.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend / Test (playwright)

[chromium] › e2e/routes/crate/version/source-link.spec.ts:5:7 › Route | crate.version | source link › show docs.rs source link even if non-docs.rs documentation link is specified @routes

1) [chromium] › e2e/routes/crate/version/source-link.spec.ts:5:7 › Route | crate.version | source link › show docs.rs source link even if non-docs.rs documentation link is specified @routes Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toHaveAttribute(expected) failed Locator: locator('[data-test-source-link] a') Expected: "https://docs.rs/crate/foo/1.0.0/source/" Timeout: 5000ms Error: element(s) not found Call log: - Expect "toHaveAttribute" with timeout 5000ms - waiting for locator('[data-test-source-link] a') 8 | 9 | await page.goto('/crates/foo'); > 10 | await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source/'); | ^ 11 | }); 12 | 13 | test('show no source link if there are no related docs.rs builds', async ({ at /home/runner/work/crates.io/crates.io/e2e/routes/crate/version/source-link.spec.ts:10:61

Check failure on line 10 in e2e/routes/crate/version/source-link.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend / Test (playwright)

[chromium] › e2e/routes/crate/version/source-link.spec.ts:5:7 › Route | crate.version | source link › show docs.rs source link even if non-docs.rs documentation link is specified @routes

1) [chromium] › e2e/routes/crate/version/source-link.spec.ts:5:7 › Route | crate.version | source link › show docs.rs source link even if non-docs.rs documentation link is specified @routes Error: expect(locator).toHaveAttribute(expected) failed Locator: locator('[data-test-source-link] a') Expected: "https://docs.rs/crate/foo/1.0.0/source/" Timeout: 5000ms Error: element(s) not found Call log: - Expect "toHaveAttribute" with timeout 5000ms - waiting for locator('[data-test-source-link] a') 8 | 9 | await page.goto('/crates/foo'); > 10 | await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source/'); | ^ 11 | }); 12 | 13 | test('show no source link if there are no related docs.rs builds', async ({ at /home/runner/work/crates.io/crates.io/e2e/routes/crate/version/source-link.spec.ts:10:61
});

test('show no source link if there are no related docs.rs builds', async ({
page,
msw,
}) => {
let crate = await msw.db.crate.create({ name: 'foo' });
await msw.db.version.create({ crate, num: '1.0.0' });

let error = HttpResponse.text('not found', { status: 404 });
msw.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => error));

await page.goto('/crates/foo');
await expect(page.getByRole('link', { name: 'crates.io', exact: true })).toHaveCount(1);

await expect(page.locator('[data-test-source-link] a')).toHaveCount(0);
});

test('show source link if `documentation` is unspecified and there are related docs.rs builds', async ({
page,
msw,
}) => {
let crate = await msw.db.crate.create({ name: 'foo' });
await msw.db.version.create({ crate, num: '1.0.0' });

let response = HttpResponse.json({
doc_status: true,
version: '1.0.0',
});
msw.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => response));

await page.goto('/crates/foo');
await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source/');
});

test('show no source link if `documentation` points to docs.rs and there are no related docs.rs builds', async ({
page,
msw,
}) => {
let crate = await msw.db.crate.create({ name: 'foo', documentation: 'https://docs.rs/foo/0.6.2' });
await msw.db.version.create({ crate, num: '1.0.0' });

let error = HttpResponse.text('not found', { status: 404 });
msw.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => error));

await page.goto('/crates/foo');
await expect(page.locator('[data-test-source-link] a')).toHaveCount(0);
});

test('show source link if `documentation` points to docs.rs and there are related docs.rs builds', async ({
page,
msw,
}) => {
let crate = await msw.db.crate.create({ name: 'foo', documentation: 'https://docs.rs/foo/0.6.2' });
await msw.db.version.create({ crate, num: '1.0.0' });

let response = HttpResponse.json({
doc_status: true,
version: '1.0.0',
});
msw.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => response));

await page.goto('/crates/foo');
await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source');

Check failure on line 74 in e2e/routes/crate/version/source-link.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend / Test (playwright)

[chromium] › e2e/routes/crate/version/source-link.spec.ts:60:7 › Route | crate.version | source link › show source link if `documentation` points to docs.rs and there are related docs.rs builds @routes

2) [chromium] › e2e/routes/crate/version/source-link.spec.ts:60:7 › Route | crate.version | source link › show source link if `documentation` points to docs.rs and there are related docs.rs builds @routes Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toHaveAttribute(expected) failed Locator: locator('[data-test-source-link] a') Expected: "https://docs.rs/crate/foo/1.0.0/source" Received: "https://docs.rs/crate/foo/1.0.0/source/" Timeout: 5000ms Call log: - Expect "toHaveAttribute" with timeout 5000ms - waiting for locator('[data-test-source-link] a') 7 × locator resolved to <a data-test-link="" class="link_e46182c89" href="https://docs.rs/crate/foo/1.0.0/source/">↵ docs.rs/crate/foo/1.0.0/source↵ </a> - unexpected value "https://docs.rs/crate/foo/1.0.0/source/" 72 | 73 | await page.goto('/crates/foo'); > 74 | await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source'); | ^ 75 | }); 76 | 77 | test('ajax errors are ignored, but show no source link', async ({ page, msw }) => { at /home/runner/work/crates.io/crates.io/e2e/routes/crate/version/source-link.spec.ts:74:61

Check failure on line 74 in e2e/routes/crate/version/source-link.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend / Test (playwright)

[chromium] › e2e/routes/crate/version/source-link.spec.ts:60:7 › Route | crate.version | source link › show source link if `documentation` points to docs.rs and there are related docs.rs builds @routes

2) [chromium] › e2e/routes/crate/version/source-link.spec.ts:60:7 › Route | crate.version | source link › show source link if `documentation` points to docs.rs and there are related docs.rs builds @routes Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toHaveAttribute(expected) failed Locator: locator('[data-test-source-link] a') Expected: "https://docs.rs/crate/foo/1.0.0/source" Received: "https://docs.rs/crate/foo/1.0.0/source/" Timeout: 5000ms Call log: - Expect "toHaveAttribute" with timeout 5000ms - waiting for locator('[data-test-source-link] a') 6 × locator resolved to <a data-test-link="" class="link_e46182c89" href="https://docs.rs/crate/foo/1.0.0/source/">↵ docs.rs/crate/foo/1.0.0/source↵ </a> - unexpected value "https://docs.rs/crate/foo/1.0.0/source/" 72 | 73 | await page.goto('/crates/foo'); > 74 | await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source'); | ^ 75 | }); 76 | 77 | test('ajax errors are ignored, but show no source link', async ({ page, msw }) => { at /home/runner/work/crates.io/crates.io/e2e/routes/crate/version/source-link.spec.ts:74:61

Check failure on line 74 in e2e/routes/crate/version/source-link.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend / Test (playwright)

[chromium] › e2e/routes/crate/version/source-link.spec.ts:60:7 › Route | crate.version | source link › show source link if `documentation` points to docs.rs and there are related docs.rs builds @routes

2) [chromium] › e2e/routes/crate/version/source-link.spec.ts:60:7 › Route | crate.version | source link › show source link if `documentation` points to docs.rs and there are related docs.rs builds @routes Error: expect(locator).toHaveAttribute(expected) failed Locator: locator('[data-test-source-link] a') Expected: "https://docs.rs/crate/foo/1.0.0/source" Received: "https://docs.rs/crate/foo/1.0.0/source/" Timeout: 5000ms Call log: - Expect "toHaveAttribute" with timeout 5000ms - waiting for locator('[data-test-source-link] a') 6 × locator resolved to <a data-test-link="" class="link_e46182c89" href="https://docs.rs/crate/foo/1.0.0/source/">↵ docs.rs/crate/foo/1.0.0/source↵ </a> - unexpected value "https://docs.rs/crate/foo/1.0.0/source/" 72 | 73 | await page.goto('/crates/foo'); > 74 | await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source'); | ^ 75 | }); 76 | 77 | test('ajax errors are ignored, but show no source link', async ({ page, msw }) => { at /home/runner/work/crates.io/crates.io/e2e/routes/crate/version/source-link.spec.ts:74:61
});

test('ajax errors are ignored, but show no source link', async ({ page, msw }) => {
let crate = await msw.db.crate.create({ name: 'foo', documentation: 'https://docs.rs/foo/0.6.2' });
await msw.db.version.create({ crate, num: '1.0.0' });

let error = HttpResponse.text('error', { status: 500 });
msw.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => error));

await page.goto('/crates/foo');
await expect(page.locator('[data-test-source-link] a')).toHaveCount(0);
});

test('empty docs.rs responses are ignored, still show source link', async ({ page, msw }) => {
let crate = await msw.db.crate.create({ name: 'foo', documentation: 'https://docs.rs/foo/0.6.2' });
await msw.db.version.create({ crate, num: '0.6.2' });

let response = HttpResponse.json({});
msw.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => response));

await page.goto('/crates/foo');
await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/0.6.2/source/');

Check failure on line 96 in e2e/routes/crate/version/source-link.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend / Test (playwright)

[chromium] › e2e/routes/crate/version/source-link.spec.ts:88:7 › Route | crate.version | source link › empty docs.rs responses are ignored

3) [chromium] › e2e/routes/crate/version/source-link.spec.ts:88:7 › Route | crate.version | source link › empty docs.rs responses are ignored, still show source link @routes Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toHaveAttribute(expected) failed Locator: locator('[data-test-source-link] a') Expected: "https://docs.rs/crate/foo/0.6.2/source/" Timeout: 5000ms Error: element(s) not found Call log: - Expect "toHaveAttribute" with timeout 5000ms - waiting for locator('[data-test-source-link] a') 94 | 95 | await page.goto('/crates/foo'); > 96 | await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/0.6.2/source/'); | ^ 97 | }); 98 | }); 99 | at /home/runner/work/crates.io/crates.io/e2e/routes/crate/version/source-link.spec.ts:96:61

Check failure on line 96 in e2e/routes/crate/version/source-link.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend / Test (playwright)

[chromium] › e2e/routes/crate/version/source-link.spec.ts:88:7 › Route | crate.version | source link › empty docs.rs responses are ignored

3) [chromium] › e2e/routes/crate/version/source-link.spec.ts:88:7 › Route | crate.version | source link › empty docs.rs responses are ignored, still show source link @routes Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toHaveAttribute(expected) failed Locator: locator('[data-test-source-link] a') Expected: "https://docs.rs/crate/foo/0.6.2/source/" Timeout: 5000ms Error: element(s) not found Call log: - Expect "toHaveAttribute" with timeout 5000ms - waiting for locator('[data-test-source-link] a') 94 | 95 | await page.goto('/crates/foo'); > 96 | await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/0.6.2/source/'); | ^ 97 | }); 98 | }); 99 | at /home/runner/work/crates.io/crates.io/e2e/routes/crate/version/source-link.spec.ts:96:61

Check failure on line 96 in e2e/routes/crate/version/source-link.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend / Test (playwright)

[chromium] › e2e/routes/crate/version/source-link.spec.ts:88:7 › Route | crate.version | source link › empty docs.rs responses are ignored

3) [chromium] › e2e/routes/crate/version/source-link.spec.ts:88:7 › Route | crate.version | source link › empty docs.rs responses are ignored, still show source link @routes Error: expect(locator).toHaveAttribute(expected) failed Locator: locator('[data-test-source-link] a') Expected: "https://docs.rs/crate/foo/0.6.2/source/" Timeout: 5000ms Error: element(s) not found Call log: - Expect "toHaveAttribute" with timeout 5000ms - waiting for locator('[data-test-source-link] a') 94 | 95 | await page.goto('/crates/foo'); > 96 | await expect(page.locator('[data-test-source-link] a')).toHaveAttribute('href', 'https://docs.rs/crate/foo/0.6.2/source/'); | ^ 97 | }); 98 | }); 99 | at /home/runner/work/crates.io/crates.io/e2e/routes/crate/version/source-link.spec.ts:96:61
});
});
84 changes: 84 additions & 0 deletions tests/routes/crate/version/source-link-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { visit } from '@ember/test-helpers';
import { module, test } from 'qunit';

import { http, HttpResponse } from 'msw';

import { setupApplicationTest } from 'crates-io/tests/helpers';

module('Route | crate.version | source link', function (hooks) {
setupApplicationTest(hooks);

test('shows docs.rs source link even if non-docs.rs documentation link is specified', async function (assert) {
let crate = await this.db.crate.create({ name: 'foo', documentation: 'https://foo.io/docs' });
await this.db.version.create({ crate, num: '1.0.0' });

await visit('/crates/foo');
assert.dom('[data-test-source-link] a').hasAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source/');
});

test('show no source link if there are no related docs.rs builds', async function (assert) {
let crate = await this.db.crate.create({ name: 'foo' });
await this.db.version.create({ crate, num: '1.0.0' });

let error = HttpResponse.text('not found', { status: 404 });
this.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => error));

await visit('/crates/foo');
assert.dom('[data-test-source-link] a').doesNotExist();
});

test('show source link if `documentation` is unspecified and there are related docs.rs builds', async function (assert) {
let crate = await this.db.crate.create({ name: 'foo' });
await this.db.version.create({ crate, num: '1.0.0' });

let response = HttpResponse.json({ doc_status: true, version: '1.0.0' });
this.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => response));

await visit('/crates/foo');
assert.dom('[data-test-source-link] a').hasAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source/');
});

test('show no source link if `documentation` points to docs.rs and there are no related docs.rs builds', async function (assert) {
let crate = await this.db.crate.create({ name: 'foo', documentation: 'https://docs.rs/foo/0.6.2' });
await this.db.version.create({ crate, num: '1.0.0' });

let error = HttpResponse.text('not found', { status: 404 });
this.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => error));

await visit('/crates/foo');
assert.dom('[data-test-source-link] a').doesNotExist();
});

test('show source link if `documentation` points to docs.rs and there are related docs.rs builds', async function (assert) {
let crate = await this.db.crate.create({ name: 'foo', documentation: 'https://docs.rs/foo/0.6.2' });
await this.db.version.create({ crate, num: '1.0.0' });

let response = HttpResponse.json({ doc_status: true, version: '1.0.0' });
this.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => response));

await visit('/crates/foo');
assert.dom('[data-test-source-link] a').hasAttribute('href', 'https://docs.rs/crate/foo/1.0.0/source/');
});

test('ajax errors are ignored, but show no source link', async function (assert) {
let crate = await this.db.crate.create({ name: 'foo', documentation: 'https://docs.rs/foo/0.6.2' });
await this.db.version.create({ crate, num: '1.0.0' });

let error = HttpResponse.text('error', { status: 500 });
this.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => error));

await visit('/crates/foo');
assert.dom('[data-test-source-link] a').doesNotExist();
});

test('empty docs.rs responses are ignored, still show source link', async function (assert) {
let crate = await this.db.crate.create({ name: 'foo', documentation: 'https://docs.rs/foo/0.6.2' });
await this.db.version.create({ crate, num: '0.6.2' });

let response = HttpResponse.json({});
this.worker.use(http.get('https://docs.rs/crate/:crate/:version/status.json', () => response));

await visit('/crates/foo');
assert.dom('[data-test-source-link] a').hasAttribute('href', 'https://docs.rs/crate/foo/0.6.2/source/');
});
});
Loading