Skip to content
This repository was archived by the owner on Jun 1, 2022. It is now read-only.

Commit b0277f4

Browse files
committed
Load entities from different repo/dir through github API and filesystem
1 parent 75b3d9d commit b0277f4

File tree

13 files changed

+184
-83
lines changed

13 files changed

+184
-83
lines changed

src/compute/experience.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import _ from 'lodash'
22
import { Db } from 'mongodb'
33
import config from '../config'
44
import { ratioToPercentage, appendCompletionToYearlyResults } from './common'
5-
import { getEntity } from '../helpers'
5+
import { getEntity } from '../entities'
66
import { Completion, SurveyConfig } from '../types'
77
import { Filters, generateFiltersQuery } from '../filters'
88
import { computeCompletionByYear } from './generic'

src/compute/generic.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import config from '../config'
55
import { Completion, SurveyConfig } from '../types'
66
import { Filters, generateFiltersQuery } from '../filters'
77
import { ratioToPercentage } from './common'
8-
import { getEntity } from '../helpers'
8+
import { getEntity } from '../entities'
99
import { getParticipationByYearMap } from './demographics'
1010
import { useCache } from '../caching'
1111

src/compute/matrices.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import _ from 'lodash'
33
import { Db } from 'mongodb'
44
import config from '../config'
55
import { ratioToPercentage } from './common'
6-
import { getEntity } from '../helpers'
6+
import { getEntity } from '../entities'
77
import { SurveyConfig } from '../types'
88
import { ToolExperienceFilterId, toolExperienceConfigById } from './tools'
99

src/entities.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { Entity } from './types'
2+
import { Octokit } from '@octokit/core'
3+
import fetch from 'node-fetch'
4+
import yaml from 'js-yaml'
5+
import { readdir, readFile } from 'fs/promises'
6+
import last from 'lodash/last'
7+
import { logToFile } from './debug'
8+
9+
let entities: Entity[] = []
10+
11+
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
12+
13+
// load locales if not yet loaded
14+
export const loadOrGetEntities = async () => {
15+
if (entities.length === 0) {
16+
entities = await loadEntities()
17+
}
18+
return entities
19+
}
20+
21+
export const loadFromGitHub = async () => {
22+
const entities: Entity[] = []
23+
console.log(`-> loading entities repo`)
24+
25+
const options = {
26+
owner: 'StateOfJS',
27+
repo: 'entities',
28+
path: ''
29+
}
30+
31+
const contents = await octokit.request('GET /repos/{owner}/{repo}/contents/{path}', options)
32+
const files = contents.data as any[]
33+
34+
// loop over repo contents and fetch raw yaml files
35+
for (const file of files) {
36+
const extension: string = last(file?.name.split('.')) || ''
37+
if (['yml', 'yaml'].includes(extension)) {
38+
const response = await fetch(file.download_url)
39+
const contents = await response.text()
40+
try {
41+
const yamlContents: any = yaml.load(contents)
42+
const category = file.name.replace('./', '').replace('.yml', '')
43+
yamlContents.forEach((entity: Entity) => {
44+
const tags = entity.tags ? [...entity.tags, category] : [category]
45+
entities.push({
46+
...entity,
47+
category,
48+
tags
49+
})
50+
})
51+
} catch (error) {
52+
console.log(`// Error loading file ${file.name}`)
53+
console.log(error)
54+
}
55+
}
56+
}
57+
return entities
58+
}
59+
60+
// when developing locally, load from local files
61+
export const loadLocally = async () => {
62+
console.log(`-> loading entities locally`)
63+
64+
const entities: Entity[] = []
65+
66+
const devDir = __dirname.split('/').slice(1, -2).join('/')
67+
const path = `/${devDir}/stateof-entities/`
68+
const files = await readdir(path)
69+
const yamlFiles = files.filter((f: String) => f.includes('.yml'))
70+
71+
// loop over dir contents and fetch raw yaml files
72+
for (const fileName of yamlFiles) {
73+
const filePath = path + '/' + fileName
74+
const contents = await readFile(filePath, 'utf8')
75+
const yamlContents: any = yaml.load(contents)
76+
const category = fileName.replace('./', '').replace('.yml', '')
77+
yamlContents.forEach((entity: Entity) => {
78+
const tags = entity.tags ? [...entity.tags, category] : [category]
79+
entities.push({
80+
...entity,
81+
category,
82+
tags
83+
})
84+
})
85+
}
86+
87+
return entities
88+
}
89+
90+
// load locales contents through GitHub API or locally
91+
export const loadEntities = async () => {
92+
console.log('// loading entities')
93+
94+
const entities: Entity[] =
95+
process.env.LOAD_LOCALES === 'local' ? await loadLocally() : await loadFromGitHub()
96+
console.log('// done loading entities')
97+
98+
return entities
99+
}
100+
101+
export const initEntities = async () => {
102+
console.log('// initializing locales…')
103+
const entities = await loadOrGetEntities()
104+
logToFile('entities.json', entities, { mode: 'overwrite' })
105+
}
106+
107+
export const getEntities = async ({ type, tag, tags }: { type?: string; tag?: string, tags?: string[] }) => {
108+
let entities = await loadOrGetEntities()
109+
if (type) {
110+
entities = entities.filter(e => e.type === type)
111+
}
112+
if (tag) {
113+
entities = entities.filter(e => e.tags && e.tags.includes(tag))
114+
}
115+
if (tags) {
116+
entities = entities.filter(e => tags.every(t => e.tags && e.tags.includes(t)))
117+
}
118+
return entities
119+
}
120+
121+
// Look up entities by id, name, or aliases (case-insensitive)
122+
export const getEntity = async ({ id }: { id: string }) => {
123+
const entities = await loadOrGetEntities()
124+
125+
if (!id || typeof id !== 'string') {
126+
return
127+
}
128+
129+
const lowerCaseId = id.toLowerCase()
130+
const entity = entities.find(e => {
131+
return (
132+
(e.id && e.id.toLowerCase() === lowerCaseId) ||
133+
(e.id && e.id.toLowerCase().replace(/\-/g, '_') === lowerCaseId) ||
134+
(e.name && e.name.toLowerCase() === lowerCaseId) ||
135+
(e.aliases && e.aliases.find((a: string) => a.toLowerCase() === lowerCaseId))
136+
)
137+
})
138+
139+
return entity || {}
140+
}

src/helpers.ts

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,14 @@
11
import { EnumTypeDefinitionNode } from 'graphql'
22
import typeDefs from './type_defs/schema.graphql'
3-
import allEntities from './data/entities/index'
3+
// import allEntities from './data/entities/index'
44
import { RequestContext, ResolverDynamicConfig, SurveyConfig } from './types'
55
import {
66
computeTermAggregationAllYearsWithCache,
77
computeTermAggregationSingleYearWithCache
88
} from './compute'
99
import { Filters } from './filters'
10+
import {loadOrGetEntities} from './entities'
1011

11-
export const getEntities = ({ type, tag, tags }: { type?: string; tag?: string, tags?: string[] }) => {
12-
let entities = allEntities
13-
if (type) {
14-
entities = entities.filter(e => e.type === type)
15-
}
16-
if (tag) {
17-
entities = entities.filter(e => e.tags && e.tags.includes(tag))
18-
}
19-
if (tags) {
20-
entities = entities.filter(e => tags.every(t => e.tags && e.tags.includes(t)))
21-
}
22-
return entities
23-
}
24-
25-
// Look up entities by id, name, or aliases (case-insensitive)
26-
export const getEntity = ({ id }: { id: string }) => {
27-
if (!id || typeof id !== 'string') {
28-
return
29-
}
30-
31-
const lowerCaseId = id.toLowerCase()
32-
const entity = allEntities.find(e => {
33-
return (
34-
(e.id && e.id.toLowerCase() === lowerCaseId) ||
35-
(e.id && e.id.toLowerCase().replace(/\-/g, '_') === lowerCaseId) ||
36-
(e.name && e.name.toLowerCase() === lowerCaseId) ||
37-
(e.aliases && e.aliases.find((a: string) => a.toLowerCase() === lowerCaseId))
38-
)
39-
})
40-
41-
return entity || {}
42-
}
4312

4413
/**
4514
* Return either e.g. other_tools.browsers.choices or other_tools.browsers.others_normalized

src/i18n.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { EnumTypeDefinitionNode } from 'graphql'
22
import { Entity, StringFile, Locale, TranslationStringObject } from './types'
3-
import entities from './data/entities/index'
43
import typeDefs from './type_defs/schema.graphql'
54
import { Octokit } from '@octokit/core'
65
import fetch from 'node-fetch'
@@ -10,6 +9,7 @@ import marked from 'marked'
109
import { logToFile } from './debug'
1110
import { readdir, readFile } from 'fs/promises'
1211
import last from 'lodash/last'
12+
import { loadOrGetEntities } from './entities'
1313

1414
let locales: Locale[] = []
1515

@@ -68,6 +68,7 @@ export const loadFromGitHub = async (localesWithRepos: any) => {
6868
}
6969
return locales
7070
}
71+
7172
// when developing locally, load from local files
7273
export const loadLocally = async (localesWithRepos: any) => {
7374
let i = 0
@@ -119,25 +120,6 @@ export const loadLocales = async () => {
119120
return locales
120121
}
121122

122-
// Look up entities by id, name, or aliases (case-insensitive)
123-
export const getEntity = ({ id }: { id: string }) => {
124-
if (!id || typeof id !== 'string') {
125-
return
126-
}
127-
128-
const lowerCaseId = id.toLowerCase()
129-
const entity = entities.find(e => {
130-
return (
131-
(e.id && e.id.toLowerCase() === lowerCaseId) ||
132-
(e.id && e.id.toLowerCase().replace(/\-/g, '_') === lowerCaseId) ||
133-
(e.name && e.name.toLowerCase() === lowerCaseId) ||
134-
(e.aliases && e.aliases.find((a: string) => a.toLowerCase() === lowerCaseId))
135-
)
136-
})
137-
138-
return entity || {}
139-
}
140-
141123
/**
142124
* Return either e.g. other_tools.browsers.choices or other_tools.browsers.others_normalized
143125
*/

src/resolvers/features.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Db } from 'mongodb'
22
import { useCache } from '../caching'
33
import { fetchMdnResource } from '../external_apis'
4-
import features from '../data/entities/features.yml'
54
import { RequestContext, SurveyConfig } from '../types'
65
import { computeTermAggregationByYear } from '../compute'
76
import { Filters } from '../filters'
87
import { Entity } from '../types'
8+
import { getEntities } from '../entities'
99

1010
const computeFeatureExperience = async (
1111
db: Db,
@@ -32,12 +32,14 @@ export default {
3232
}
3333
},
3434
Feature: {
35-
name: ({ id }: { id: string }) => {
35+
name: async ({ id }: { id: string }) => {
36+
const features = await getEntities({ tag: 'feature' })
3637
const feature = features.find((f: Entity) => f.id === id)
3738

3839
return feature && feature.name
3940
},
4041
mdn: async ({ id }: { id: string }) => {
42+
const features = await getEntities({ tag: 'feature' })
4143
const feature = features.find((f: Entity) => f.id === id)
4244
if (!feature || !feature.mdn) {
4345
return

src/resolvers/features_others.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Db } from 'mongodb'
2-
import features from '../data/entities/features.yml'
32
import { useCache } from '../caching'
43
import { computeTermAggregationByYear } from '../compute'
54
import { getOtherKey } from '../helpers'
65
import { RequestContext, SurveyConfig } from '../types'
76
import { Filters } from '../filters'
87
import { Entity } from '../types'
8+
import { getEntities } from '../entities'
99

1010
interface OtherFeaturesConfig {
1111
survey: SurveyConfig
@@ -19,6 +19,8 @@ const computeOtherFeatures = async (
1919
id: string,
2020
filters?: Filters
2121
) => {
22+
const features = await getEntities({ tag: 'feature'})
23+
2224
const otherFeaturesByYear = await useCache(computeTermAggregationByYear, db, [
2325
survey,
2426
`features_others.${getOtherKey(id)}`,

src/resolvers/query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { SurveyType } from '../types'
2-
import { getEntities, getEntity } from '../helpers'
2+
import { getEntities, getEntity } from '../entities'
33
import { getLocales, getLocaleObject, getTranslation } from '../i18n'
44
import { SurveyConfig } from '../types'
55

src/resolvers/surveys.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { getEntity, getGraphQLEnumValues, getDemographicsResolvers } from '../helpers'
1+
import { getGraphQLEnumValues, getDemographicsResolvers } from '../helpers'
2+
import { getEntity } from '../entities'
23
import { RequestContext, SurveyConfig } from '../types'
34
import { Filters } from '../filters'
4-
import {computeToolExperienceGraph, computeToolsCardinalityByUser, ToolExperienceId} from '../compute'
5+
import {
6+
computeToolExperienceGraph,
7+
computeToolsCardinalityByUser,
8+
ToolExperienceId
9+
} from '../compute'
510
import { useCache } from '../caching'
611

712
const toolIds = getGraphQLEnumValues('ToolID')
@@ -77,10 +82,7 @@ export default {
7782
id,
7883
filters
7984
}),
80-
happiness: (
81-
survey: SurveyConfig,
82-
{ id, filters }: { id: string; filters?: Filters }
83-
) => ({
85+
happiness: (survey: SurveyConfig, { id, filters }: { id: string; filters?: Filters }) => ({
8486
survey,
8587
id,
8688
filters
@@ -141,20 +143,20 @@ export default {
141143
{ db }: RequestContext
142144
) => useCache(computeToolExperienceGraph, db, [survey, id, filters])
143145
})),
144-
tools_cardinality_by_user: (survey: SurveyConfig, {
145-
year,
146-
// tool IDs
147-
ids,
148-
experienceId,
149-
}: {
150-
year: number
151-
ids: string[]
152-
experienceId: ToolExperienceId
153-
}, context: RequestContext) => useCache(
154-
computeToolsCardinalityByUser,
155-
context.db,
156-
[survey, year, ids, experienceId]
157-
),
146+
tools_cardinality_by_user: (
147+
survey: SurveyConfig,
148+
{
149+
year,
150+
// tool IDs
151+
ids,
152+
experienceId
153+
}: {
154+
year: number
155+
ids: string[]
156+
experienceId: ToolExperienceId
157+
},
158+
context: RequestContext
159+
) => useCache(computeToolsCardinalityByUser, context.db, [survey, year, ids, experienceId]),
158160
tools_others: (
159161
survey: SurveyConfig,
160162
{ id, filters }: { id: string; filters?: Filters }
@@ -171,6 +173,6 @@ export default {
171173
ids,
172174
filters
173175
}),
174-
totals: (survey: SurveyConfig) => survey,
176+
totals: (survey: SurveyConfig) => survey
175177
}
176178
}

0 commit comments

Comments
 (0)