Skip to content

Commit 10194ab

Browse files
committed
Allows specifying default specification via config
The default specification is the one displayed when no specification has been selected via the URL or when the project is opened from the sidebar.
1 parent 19924a0 commit 10194ab

File tree

7 files changed

+210
-21
lines changed

7 files changed

+210
-21
lines changed

__test__/projects/GitHubProjectDataSource.test.ts

Lines changed: 188 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ test("It maps projects including branches and tags", async () => {
8787
id: "openapi.yml",
8888
name: "openapi.yml",
8989
url: "/api/blob/acme/foo-openapi/openapi.yml?ref=12345678",
90-
editURL: "https://github.com/acme/foo-openapi/edit/main/openapi.yml"
90+
editURL: "https://github.com/acme/foo-openapi/edit/main/openapi.yml",
91+
isDefault: false
9192
}],
9293
url: "https://github.com/acme/foo-openapi/tree/main",
9394
isDefault: true
@@ -98,7 +99,8 @@ test("It maps projects including branches and tags", async () => {
9899
id: "openapi.yml",
99100
name: "openapi.yml",
100101
url: "/api/blob/acme/foo-openapi/openapi.yml?ref=12345678",
101-
editURL: "https://github.com/acme/foo-openapi/edit/1.0/openapi.yml"
102+
editURL: "https://github.com/acme/foo-openapi/edit/1.0/openapi.yml",
103+
isDefault: false
102104
}],
103105
url: "https://github.com/acme/foo-openapi/tree/1.0",
104106
isDefault: false
@@ -195,17 +197,20 @@ test("It supports multiple OpenAPI specifications on a branch", async () => {
195197
id: "foo-service.yml",
196198
name: "foo-service.yml",
197199
url: "/api/blob/acme/foo-openapi/foo-service.yml?ref=12345678",
198-
editURL: "https://github.com/acme/foo-openapi/edit/main/foo-service.yml"
200+
editURL: "https://github.com/acme/foo-openapi/edit/main/foo-service.yml",
201+
isDefault: false
199202
}, {
200203
id: "bar-service.yml",
201204
name: "bar-service.yml",
202205
url: "/api/blob/acme/foo-openapi/bar-service.yml?ref=12345678",
203-
editURL: "https://github.com/acme/foo-openapi/edit/main/bar-service.yml"
206+
editURL: "https://github.com/acme/foo-openapi/edit/main/bar-service.yml",
207+
isDefault: false
204208
}, {
205209
id: "baz-service.yml",
206210
name: "baz-service.yml",
207211
url: "/api/blob/acme/foo-openapi/baz-service.yml?ref=12345678",
208-
editURL: "https://github.com/acme/foo-openapi/edit/main/baz-service.yml"
212+
editURL: "https://github.com/acme/foo-openapi/edit/main/baz-service.yml",
213+
isDefault: false
209214
}],
210215
url: "https://github.com/acme/foo-openapi/tree/main",
211216
isDefault: true
@@ -216,7 +221,8 @@ test("It supports multiple OpenAPI specifications on a branch", async () => {
216221
id: "openapi.yml",
217222
name: "openapi.yml",
218223
url: "/api/blob/acme/foo-openapi/openapi.yml?ref=12345678",
219-
editURL: "https://github.com/acme/foo-openapi/edit/1.0/openapi.yml"
224+
editURL: "https://github.com/acme/foo-openapi/edit/1.0/openapi.yml",
225+
isDefault: false
220226
}],
221227
url: "https://github.com/acme/foo-openapi/tree/1.0",
222228
isDefault: false
@@ -749,11 +755,13 @@ test("It adds remote versions from the project configuration", async () => {
749755
specifications: [{
750756
id: "huey",
751757
name: "Huey",
752-
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/huey.yml" })}`
758+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/huey.yml" })}`,
759+
isDefault: false
753760
}, {
754761
id: "dewey",
755762
name: "Dewey",
756-
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/dewey.yml" })}`
763+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/dewey.yml" })}`,
764+
isDefault: false
757765
}]
758766
}, {
759767
id: "bobby",
@@ -762,7 +770,8 @@ test("It adds remote versions from the project configuration", async () => {
762770
specifications: [{
763771
id: "louie",
764772
name: "Louie",
765-
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/louie.yml" })}`
773+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/louie.yml" })}`,
774+
isDefault: false
766775
}]
767776
}])
768777
})
@@ -816,7 +825,8 @@ test("It modifies ID of remote version if the ID already exists", async () => {
816825
id: "openapi.yml",
817826
name: "openapi.yml",
818827
url: "/api/blob/acme/foo-openapi/openapi.yml?ref=12345678",
819-
editURL: "https://github.com/acme/foo-openapi/edit/bar/openapi.yml"
828+
editURL: "https://github.com/acme/foo-openapi/edit/bar/openapi.yml",
829+
isDefault: false
820830
}]
821831
}, {
822832
id: "bar1",
@@ -825,7 +835,8 @@ test("It modifies ID of remote version if the ID already exists", async () => {
825835
specifications: [{
826836
id: "baz",
827837
name: "Baz",
828-
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`
838+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`,
839+
isDefault: false
829840
}]
830841
}, {
831842
id: "bar2",
@@ -834,7 +845,8 @@ test("It modifies ID of remote version if the ID already exists", async () => {
834845
specifications: [{
835846
id: "hello",
836847
name: "Hello",
837-
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/hello.yml" })}`
848+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/hello.yml" })}`,
849+
isDefault: false
838850
}]
839851
}])
840852
})
@@ -877,7 +889,8 @@ test("It lets users specify the ID of a remote version", async () => {
877889
specifications: [{
878890
id: "baz",
879891
name: "Baz",
880-
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`
892+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`,
893+
isDefault: false
881894
}]
882895
}])
883896
})
@@ -920,7 +933,168 @@ test("It lets users specify the ID of a remote specification", async () => {
920933
specifications: [{
921934
id: "some-spec",
922935
name: "Baz",
923-
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`
936+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`,
937+
isDefault: false
924938
}]
925939
}])
926940
})
941+
942+
test("It sets isDefault on the correct specification based on defaultSpecificationName in config", async () => {
943+
const sut = new GitHubProjectDataSource({
944+
repositoryNameSuffix: "-openapi",
945+
repositoryDataSource: {
946+
async getRepositories() {
947+
return [{
948+
owner: "acme",
949+
name: "foo-openapi",
950+
defaultBranchRef: {
951+
id: "12345678",
952+
name: "main"
953+
},
954+
configYml: {
955+
text: `
956+
defaultSpecificationName: bar-service.yml
957+
remoteVersions:
958+
- name: Bar
959+
specifications:
960+
- id: some-spec
961+
name: Baz
962+
url: https://example.com/baz.yml
963+
`
964+
},
965+
branches: [{
966+
id: "12345678",
967+
name: "main",
968+
files: [
969+
{ name: "foo-service.yml" },
970+
{ name: "bar-service.yml" },
971+
{ name: "baz-service.yml" }
972+
]
973+
}],
974+
tags: []
975+
}]
976+
}
977+
},
978+
encryptionService: noopEncryptionService,
979+
remoteConfigEncoder: base64RemoteConfigEncoder
980+
})
981+
const projects = await sut.getProjects()
982+
const specs = projects[0].versions[0].specifications
983+
expect(specs.find(s => s.name === "bar-service.yml")!.isDefault).toBe(true)
984+
expect(specs.find(s => s.name === "foo-service.yml")!.isDefault).toBe(false)
985+
expect(specs.find(s => s.name === "baz-service.yml")!.isDefault).toBe(false)
986+
expect(projects[0].versions[1].specifications.find(s => s.name === "Baz")!.isDefault).toBe(false)
987+
})
988+
989+
test("It sets a remote specification as the default if specified", async () => {
990+
const sut = new GitHubProjectDataSource({
991+
repositoryNameSuffix: "-openapi",
992+
repositoryDataSource: {
993+
async getRepositories() {
994+
return [{
995+
owner: "acme",
996+
name: "foo-openapi",
997+
defaultBranchRef: {
998+
id: "12345678",
999+
name: "main"
1000+
},
1001+
configYaml: {
1002+
text: `
1003+
defaultSpecificationName: Baz
1004+
remoteVersions:
1005+
- name: Bar
1006+
specifications:
1007+
- id: some-spec
1008+
name: Baz
1009+
url: https://example.com/baz.yml
1010+
- id: another-spec
1011+
name: Qux
1012+
url: https://example.com/qux.yml
1013+
`
1014+
},
1015+
branches: [],
1016+
tags: []
1017+
}]
1018+
}
1019+
},
1020+
encryptionService: noopEncryptionService,
1021+
remoteConfigEncoder: base64RemoteConfigEncoder
1022+
})
1023+
const projects = await sut.getProjects()
1024+
const remoteSpecs = projects[0].versions[0].specifications
1025+
expect(remoteSpecs.find(s => s.id === "some-spec")!.isDefault).toBe(true)
1026+
expect(remoteSpecs.find(s => s.id === "another-spec")!.isDefault).toBe(false)
1027+
})
1028+
1029+
1030+
test("It sets isDefault to false for all specifications if defaultSpecificationName is not set", async () => {
1031+
const sut = new GitHubProjectDataSource({
1032+
repositoryNameSuffix: "-openapi",
1033+
repositoryDataSource: {
1034+
async getRepositories() {
1035+
return [{
1036+
owner: "acme",
1037+
name: "foo-openapi",
1038+
defaultBranchRef: {
1039+
id: "12345678",
1040+
name: "main"
1041+
},
1042+
configYml: {
1043+
text: ``
1044+
},
1045+
branches: [{
1046+
id: "12345678",
1047+
name: "main",
1048+
files: [
1049+
{ name: "foo-service.yml" },
1050+
{ name: "bar-service.yml" },
1051+
{ name: "baz-service.yml" }
1052+
]
1053+
}],
1054+
tags: []
1055+
}]
1056+
}
1057+
},
1058+
encryptionService: noopEncryptionService,
1059+
remoteConfigEncoder: base64RemoteConfigEncoder
1060+
})
1061+
const projects = await sut.getProjects()
1062+
const specs = projects[0].versions[0].specifications
1063+
expect(specs.every(s => s.isDefault === false)).toBe(true)
1064+
})
1065+
1066+
test("It silently ignores defaultSpecificationName if no matching spec is found", async () => {
1067+
const sut = new GitHubProjectDataSource({
1068+
repositoryNameSuffix: "-openapi",
1069+
repositoryDataSource: {
1070+
async getRepositories() {
1071+
return [{
1072+
owner: "acme",
1073+
name: "foo-openapi",
1074+
defaultBranchRef: {
1075+
id: "12345678",
1076+
name: "main"
1077+
},
1078+
configYml: {
1079+
text: `defaultSpecificationName: non-existent.yml`
1080+
},
1081+
branches: [{
1082+
id: "12345678",
1083+
name: "main",
1084+
files: [
1085+
{ name: "foo-service.yml" },
1086+
{ name: "bar-service.yml" },
1087+
{ name: "baz-service.yml" }
1088+
]
1089+
}],
1090+
tags: []
1091+
}]
1092+
}
1093+
},
1094+
encryptionService: noopEncryptionService,
1095+
remoteConfigEncoder: base64RemoteConfigEncoder
1096+
})
1097+
const projects = await sut.getProjects()
1098+
const specs = projects[0].versions[0].specifications
1099+
expect(specs.every(s => s.isDefault === false)).toBe(true)
1100+
})

src/features/projects/data/GitHubProjectDataSource.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export default class GitHubProjectDataSource implements IProjectDataSource {
6565
).filter(version => {
6666
return version.specifications.length > 0
6767
})
68+
.map(version => this.setDefaultSpecification(version, config?.defaultSpecificationName));
6869
const defaultName = repository.name.replace(new RegExp(this.repositoryNameSuffix + "$"), "")
6970
return {
7071
id: `${repository.owner}-${defaultName}`,
@@ -130,7 +131,8 @@ export default class GitHubProjectDataSource implements IProjectDataSource {
130131
path: file.name,
131132
ref: ref.id
132133
}),
133-
editURL: `https://github.com/${ownerName}/${repositoryName}/edit/${ref.name}/${file.name}`
134+
editURL: `https://github.com/${ownerName}/${repositoryName}/edit/${ref.name}/${file.name}`,
135+
isDefault: false // initial value
134136
}
135137
})
136138
return {
@@ -187,7 +189,8 @@ export default class GitHubProjectDataSource implements IProjectDataSource {
187189
return {
188190
id: this.makeURLSafeID((e.id || e.name).toLowerCase()),
189191
name: e.name,
190-
url: `/api/remotes/${encodedRemoteConfig}`
192+
url: `/api/remotes/${encodedRemoteConfig}`,
193+
isDefault: false // initial value
191194
};
192195
})
193196
versions.push({
@@ -246,4 +249,14 @@ export default class GitHubProjectDataSource implements IProjectDataSource {
246249
return undefined
247250
}
248251
}
252+
253+
private setDefaultSpecification(version: Version, defaultSpecificationName?: string): Version {
254+
return {
255+
...version,
256+
specifications: version.specifications.map(spec => ({
257+
...spec,
258+
isDefault: spec.name == defaultSpecificationName
259+
}))
260+
}
261+
}
249262
}

src/features/projects/data/useProjectSelection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default function useProjectSelection() {
2929
},
3030
selectProject: (project: Project) => {
3131
const version = project.versions[0]
32-
const specification = version.specifications[0]
32+
const specification = version.specifications.find(spec => spec.isDefault) || version.specifications[0]
3333
NProgress.start()
3434
projectNavigator.navigate(
3535
project.owner,

src/features/projects/domain/IProjectConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const ProjectConfigRemoteVersionSchema = z.object({
2020
export const IProjectConfigSchema = z.object({
2121
name: z.coerce.string().optional(),
2222
image: z.string().optional(),
23+
defaultSpecificationName: z.string().optional(),
2324
remoteVersions: ProjectConfigRemoteVersionSchema.array().optional()
2425
})
2526

src/features/projects/domain/OpenApiSpecification.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ export const OpenApiSpecificationSchema = z.object({
44
id: z.string(),
55
name: z.string(),
66
url: z.string(),
7-
editURL: z.string().optional()
7+
editURL: z.string().optional(),
8+
isDefault: z.boolean()
89
})
910

1011
type OpenApiSpecification = z.infer<typeof OpenApiSpecificationSchema>

src/features/projects/domain/ProjectNavigator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ export default class ProjectNavigator {
3636
if (candidateSpecification) {
3737
this.router.push(`/${project.owner}/${project.name}/${newVersion.id}/${candidateSpecification.id}`)
3838
} else {
39-
const firstSpecification = newVersion.specifications[0]
40-
this.router.push(`/${project.owner}/${project.name}/${newVersion.id}/${firstSpecification.id}`)
39+
const defaultOrFirstSpecification = newVersion.specifications.find(spec => spec.isDefault) || newVersion.specifications[0]
40+
this.router.push(`/${project.owner}/${project.name}/${newVersion.id}/${defaultOrFirstSpecification.id}`)
4141
}
4242
}
4343

src/features/projects/domain/getProjectSelectionFromPath.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default function getProjectSelectionFromPath({
6161
if (specificationId && !didMoveSpecificationIdToVersionId) {
6262
specification = version.specifications.find(e => e.id == specificationId)
6363
} else if (version.specifications.length > 0) {
64-
specification = version.specifications[0]
64+
specification = version.specifications.find(spec => spec.isDefault) || version.specifications[0]
6565
}
6666
return { project, version, specification }
6767
}

0 commit comments

Comments
 (0)