Skip to content

Commit 78e30e3

Browse files
authored
Merge pull request #965 from ember-learn/kg-fix-version-changing
Fixes for version changing
2 parents 2fee012 + a51eece commit 78e30e3

File tree

7 files changed

+278
-68
lines changed

7 files changed

+278
-68
lines changed

app/assets/styles.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ section.event {
177177

178178
.whoops {
179179
display: flex;
180+
flex-direction: column;
180181
justify-content: center;
181182
align-items: center;
182183
padding: var(--spacing-6);

app/controllers/project-version.js

Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { action, computed, set } from '@ember/object';
33
import { inject as service } from '@ember/service';
44
import { readOnly, alias } from '@ember/object/computed';
5-
import Controller, { inject as controller } from '@ember/controller';
5+
import Controller from '@ember/controller';
66
import { A } from '@ember/array';
77
import values from 'lodash.values';
88
import groupBy from 'lodash.groupby';
@@ -25,10 +25,6 @@ export default class ProjectVersionController extends Controller {
2525
@service router;
2626
@service('project') projectService;
2727

28-
@controller('project-version.classes.class') classController;
29-
@controller('project-version.modules.module') moduleController;
30-
@controller('project-version.namespaces.namespace') namespaceController;
31-
3228
@alias('filterData.sideNav.showPrivate')
3329
showPrivateClasses;
3430

@@ -141,61 +137,61 @@ export default class ProjectVersionController extends Controller {
141137

142138
@action
143139
updateProject(project, ver /*, component */) {
144-
let projectVersionID = ver.compactVersion;
145-
let endingRoute;
146-
switch (this.router.currentRouteName) {
147-
case 'project-version.classes.class': {
148-
let className = this._getEncodedNameForCurrentClass();
149-
endingRoute = `classes/${className}`;
150-
break;
151-
}
152-
case 'project-version.modules.module': {
153-
let moduleName = encodeURIComponent(this.moduleController.model.name);
154-
endingRoute = `modules/${moduleName}`;
155-
break;
156-
}
157-
case 'project-version.namespaces.namespace': {
158-
let namespaceName = this.namespaceController.model.name;
159-
endingRoute = `namespaces/${namespaceName}`;
160-
break;
161-
}
162-
default:
163-
endingRoute = '';
164-
break;
165-
}
166-
// if the user is navigating to/from api versions >= 2.16, take them
167-
// to the home page instead of trying to translate the url
168-
let shouldConvertPackages = this._shouldConvertPackages(
169-
ver,
170-
this.projectService.version,
171-
);
172-
let isEmberProject = project === 'ember';
173-
174-
if (!isEmberProject || !shouldConvertPackages) {
175-
this.router.transitionTo(
176-
`/${project}/${projectVersionID}/${endingRoute}`,
177-
);
178-
} else {
179-
this.router.transitionTo(`/${project}/${projectVersionID}`);
180-
}
181-
}
182-
183-
_getEncodedNameForCurrentClass() {
184-
// escape any reserved characters for url, like slashes
185-
return encodeURIComponent(this.classController.model.get('name'));
186-
}
187-
188-
// Input some version info, returns a boolean based on
189-
// whether the user is switching versions for a 2.16 docs release or later.
190-
// The urls for pre-2.16 classes and later packages are quite different
191-
_shouldConvertPackages(targetVer, previousVer) {
192-
let targetVersion = getCompactVersion(targetVer.id);
193-
let previousVersion = getCompactVersion(previousVer);
194-
let previousComparison = semverCompare(previousVersion, '2.16');
195-
let targetComparison = semverCompare(targetVersion, '2.16');
196-
return (
197-
(previousComparison < 0 && targetComparison >= 0) ||
198-
(previousComparison >= 0 && targetComparison < 0)
140+
const currentURL = this.router.currentURL;
141+
this.router.transitionTo(
142+
findEndingRoute({
143+
project,
144+
targetVersion: ver.id,
145+
currentVersion: this.projectService.version,
146+
currentUrlVersion: this.projectService.getUrlVersion(),
147+
currentURL,
148+
currentAnchor: window.location.hash,
149+
}),
199150
);
200151
}
201152
}
153+
154+
export function findEndingRoute({
155+
project,
156+
targetVersion,
157+
currentVersion,
158+
currentUrlVersion,
159+
currentURL,
160+
currentAnchor,
161+
}) {
162+
let projectVersionID = getCompactVersion(targetVersion);
163+
// if the user is navigating to/from api versions Ember >= 2.16 or Ember Data >= 4.0, take them
164+
// to the home page instead of trying to translate the url
165+
if (shouldGoToVersionIndex(project, targetVersion, currentVersion)) {
166+
return `/${project}/${projectVersionID}`;
167+
} else {
168+
return `${currentURL.replace(currentUrlVersion, projectVersionID)}${currentAnchor}`;
169+
}
170+
}
171+
172+
function shouldGoToVersionIndex(project, targetVersion, currentVersion) {
173+
let boundaryVersion;
174+
if (project === 'ember') {
175+
boundaryVersion = '2.16';
176+
} else if (project === 'ember-data') {
177+
boundaryVersion = '4.0';
178+
}
179+
return isCrossingVersionBoundary(
180+
targetVersion,
181+
currentVersion,
182+
boundaryVersion,
183+
);
184+
}
185+
186+
// Input some version info, returns a boolean based on
187+
// whether the user is switching versions for a release or later.
188+
function isCrossingVersionBoundary(targetVer, previousVer, boundaryVersion) {
189+
let targetVersion = getCompactVersion(targetVer);
190+
let previousVersion = getCompactVersion(previousVer);
191+
let previousComparison = semverCompare(previousVersion, boundaryVersion);
192+
let targetComparison = semverCompare(targetVersion, boundaryVersion);
193+
return (
194+
(previousComparison < 0 && targetComparison >= 0) ||
195+
(previousComparison >= 0 && targetComparison < 0)
196+
);
197+
}

app/routes/project-version/classes/class.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,26 @@ export default class ClassRoute extends Route {
5959
});
6060
}
6161

62-
redirect(model) {
62+
redirect(model, transition) {
6363
if (model.isError) {
64+
// Transitioning to the same route, probably only changing version
65+
// Could explicitly check by comparing transition.to and transition.from
66+
if (transition.to.name === transition?.from?.name) {
67+
const projectVersionRouteInfo = transition.to.find(function (item) {
68+
return item.params?.project_version;
69+
});
70+
const attemptedVersion =
71+
projectVersionRouteInfo.params?.project_version;
72+
const attemptedProject = projectVersionRouteInfo.params?.project;
73+
let error = new Error(
74+
`We could not find ${transition.to.localName} ${transition.to.params[transition.to.paramNames[0]]} in v${attemptedVersion} of ${attemptedProject}.`,
75+
);
76+
error.status = 404;
77+
error.attemptedProject = attemptedProject;
78+
error.attemptedVersion = attemptedVersion;
79+
throw error;
80+
}
81+
6482
let error = new Error(
6583
'Error retrieving model in routes/project-version/classes/class',
6684
);

app/routes/project-version/functions/function.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default class FunctionRoute extends Route {
1717
return model?.fn?.name;
1818
}
1919

20-
async model(params) {
20+
async model(params, transition) {
2121
const pVParams = this.paramsFor('project-version');
2222
const { project, project_version: compactVersion } = pVParams;
2323

@@ -38,10 +38,24 @@ export default class FunctionRoute extends Route {
3838
`${project}-${projectVersion}-${className}`.toLowerCase(),
3939
);
4040
} catch (e) {
41-
fnModule = await this.store.find(
42-
'namespace',
43-
`${project}-${projectVersion}-${className}`.toLowerCase(),
44-
);
41+
try {
42+
fnModule = await this.store.find(
43+
'namespace',
44+
`${project}-${projectVersion}-${className}`.toLowerCase(),
45+
);
46+
} catch (e2) {
47+
if (transition.to.name === transition?.from?.name) {
48+
let error = new Error(
49+
`We could not find function ${className}/${functionName} in v${compactVersion} of ${project}.`,
50+
);
51+
error.status = 404;
52+
error.attemptedProject = project;
53+
error.attemptedVersion = compactVersion;
54+
throw error;
55+
} else {
56+
throw e2;
57+
}
58+
}
4559
}
4660

4761
return {

app/templates/error.hbs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
<article class="whoops">
22
{{#if (eq this.model.status 404)}}
33
<h2 class="whoops__title">Ack! 404 friend, you're in the wrong place</h2>
4-
<div class="whoops__message">
4+
{{#if this.model.attemptedVersion}}
5+
<h3>
6+
{{this.model.message}}
7+
</h3>
8+
<p>
9+
Modules, classes, and functions sometimes move around or are renamed across versions.
10+
Try the <LinkTo @route="project-version" @models={{array this.model.attemptedProject this.model.attemptedVersion}} data-test-version-index-link>v{{this.model.attemptedVersion}} API docs index.</LinkTo>
11+
</p>
12+
{{else}}
513
<p>
614
This page wasn't found. Please try the <LinkTo @route="index">API docs page</LinkTo>.
715
If you expected something else to be here, please file a <a href="https://github.com/ember-learn/ember-api-docs/issues/new" target="_blank" rel="noopener noreferrer">ticket</a>.
816
</p>
9-
</div>
17+
{{/if}}
1018
{{else}}
1119
<h2 class="whoops__title">
1220
Whoops! Something went wrong.

tests/acceptance/switch-versions-test.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ async function waitForSettled() {
1111
await settled();
1212
}
1313

14+
const versionIndexLinkSelector = '[data-test-version-index-link]';
15+
1416
module('Acceptance | version navigation', function (hooks) {
1517
setupApplicationTest(hooks);
1618

@@ -38,6 +40,23 @@ module('Acceptance | version navigation', function (hooks) {
3840
);
3941
});
4042

43+
test('switching versions from release', async function (assert) {
44+
await visit('/ember/release/modules/@glimmer%2Ftracking');
45+
46+
assert.equal(
47+
currentURL(),
48+
'/ember/release/modules/@glimmer%2Ftracking',
49+
'navigated to release',
50+
);
51+
await selectChoose('.ember-power-select-trigger', '6.4');
52+
53+
assert.equal(
54+
currentURL(),
55+
'/ember/6.4/modules/@glimmer%2Ftracking',
56+
'navigated to v6.4 class',
57+
);
58+
});
59+
4160
test('switching namespace versions less than 2.16 should retain namespace page', async function (assert) {
4261
await visit('/ember/2.7/namespaces/Ember');
4362
await waitForSettled();
@@ -165,6 +184,21 @@ module('Acceptance | version navigation', function (hooks) {
165184
);
166185
});
167186

187+
test('switching between versions on a function works', async function (assert) {
188+
await visit('/ember/6.5/functions/@ember%2Fdebug/debug');
189+
assert.strictEqual(
190+
currentURL(),
191+
'/ember/6.5/functions/@ember%2Fdebug/debug',
192+
);
193+
194+
await selectChoose('.ember-power-select-trigger', '6.4');
195+
196+
assert.strictEqual(
197+
currentURL(),
198+
'/ember/6.4/functions/@ember%2Fdebug/debug',
199+
);
200+
});
201+
168202
test('switching versions works if class name includes slashes', async function (assert) {
169203
await visit('/ember/3.4/classes/@ember%2Fobject%2Fcomputed');
170204
assert.equal(
@@ -279,4 +313,62 @@ module('Acceptance | version navigation', function (hooks) {
279313
'navigated to v1.13 class',
280314
);
281315
});
316+
317+
test('switching to a version that is missing a module offers a link to the API index for that version', async function (assert) {
318+
await visit('/ember/6.4/modules/@glimmer%2Ftracking%2Fprimitives%2Fcache');
319+
assert.strictEqual(
320+
currentURL(),
321+
'/ember/6.4/modules/@glimmer%2Ftracking%2Fprimitives%2Fcache',
322+
);
323+
324+
await selectChoose('.ember-power-select-trigger', '3.10');
325+
326+
assert
327+
.dom()
328+
.includesText(
329+
'We could not find module @glimmer/tracking/primitives/cache in v3.10 of ember.',
330+
);
331+
332+
assert
333+
.dom(versionIndexLinkSelector)
334+
.includesText('v3.10')
335+
.hasAttribute('href', '/ember/3.10');
336+
});
337+
338+
test('switching to a version that is missing a class offers a link to the API index for that version', async function (assert) {
339+
await visit('/ember/3.0/classes/Ember.Debug');
340+
assert.strictEqual(currentURL(), '/ember/3.0/classes/Ember.Debug');
341+
342+
await selectChoose('.ember-power-select-trigger', '4.0');
343+
344+
assert
345+
.dom()
346+
.includesText('We could not find class Ember.Debug in v4.0 of ember.');
347+
348+
assert
349+
.dom(versionIndexLinkSelector)
350+
.includesText('v4.0')
351+
.hasAttribute('href', '/ember/4.0');
352+
});
353+
354+
test('switching to a version that is missing a function offers a link to the API index for that version', async function (assert) {
355+
await visit('/ember/3.28/functions/@glimmer%2Ftracking/tracked');
356+
assert.strictEqual(
357+
currentURL(),
358+
'/ember/3.28/functions/@glimmer%2Ftracking/tracked',
359+
);
360+
361+
await selectChoose('.ember-power-select-trigger', '3.12');
362+
363+
assert
364+
.dom()
365+
.includesText(
366+
'We could not find function @glimmer/tracking/tracked in v3.12 of ember.',
367+
);
368+
369+
assert
370+
.dom(versionIndexLinkSelector)
371+
.includesText('v3.12')
372+
.hasAttribute('href', '/ember/3.12');
373+
});
282374
});

0 commit comments

Comments
 (0)