Skip to content

Commit fec30a0

Browse files
authored
Merge 03d37da into fd217bd
2 parents fd217bd + 03d37da commit fec30a0

File tree

5 files changed

+98
-79
lines changed

5 files changed

+98
-79
lines changed

public/app.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/tests/distrito-20-2.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1, shrink-to-fit=no"
8+
/>
9+
<meta name="description" content="React JS recovery meeting finder demo" />
10+
<link rel="icon" type="image/png" href="/logo.png" />
11+
<title>Meetings</title>
12+
</head>
13+
<body style="margin: 0">
14+
<div
15+
id="tsml-ui"
16+
data-src="https://eastbayaa.org/wp-admin/admin-ajax.php?action=meetings&type=S,https://contracostaaa.org/wp-admin/admin-ajax.php?action=meetings&key=4e7a148a204d11a0089b1acd0df62f81&type=S"
17+
data-timezone="America/Los_Angeles"
18+
></div>
19+
<script src="/app.js" async></script>
20+
</body>
21+
</html>

src/components/TsmlUI.tsx

Lines changed: 65 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useSearchParams } from 'react-router-dom';
66
import {
77
filterMeetingData,
88
getQueryString,
9+
isGoogleSheetData,
910
loadMeetingData,
1011
mergeSettings,
1112
SettingsContext,
@@ -24,7 +25,7 @@ import {
2425
Title,
2526
} from './';
2627

27-
import type { State } from '../types';
28+
import type { JSONData, State } from '../types';
2829

2930
export default function TsmlUI({
3031
google,
@@ -104,6 +105,20 @@ export default function TsmlUI({
104105

105106
const input = getQueryString(settings);
106107

108+
if (timezone) {
109+
// check if timezone is valid
110+
try {
111+
Intl.DateTimeFormat(undefined, { timeZone: timezone });
112+
} catch (e) {
113+
return setState(state => ({
114+
...state,
115+
error: `Timezone ${timezone} is not valid. Please use one like Europe/Rome.`,
116+
filtering: false,
117+
loading: false,
118+
}));
119+
}
120+
}
121+
107122
if (!src) {
108123
setState(state => ({
109124
...state,
@@ -112,60 +127,59 @@ export default function TsmlUI({
112127
loading: false,
113128
}));
114129
} else {
115-
const sheetId = src.startsWith(
116-
'https://docs.google.com/spreadsheets/d/'
117-
)
118-
? src.split('/')[5]
119-
: undefined;
120-
121-
// google sheet
122-
if (sheetId) {
123-
if (!google) {
124-
setState(state => ({
125-
...state,
126-
error: 'Configuration error: a Google API key is required.',
127-
filtering: false,
128-
loading: false,
129-
}));
130-
}
131-
src = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/A1:ZZ?key=${google}`;
132-
}
133-
134-
// cache busting
135-
if (src.endsWith('.json') && input.meeting) {
136-
src = `${src}?${new Date().getTime()}`;
137-
}
130+
const sheets: (string | undefined)[] = [];
131+
Promise.all(
132+
src.split(',').map(src => {
133+
const sheetId = src.startsWith(
134+
'https://docs.google.com/spreadsheets/d/'
135+
)
136+
? src.split('/')[5]
137+
: undefined;
138+
sheets.push(sheetId);
138139

139-
// fetch json data file and build indexes
140-
fetch(src)
141-
.then(res => (res.ok ? res.json() : Promise.reject(res.status)))
142-
.then(data => {
140+
// google sheet
143141
if (sheetId) {
144-
data = translateGoogleSheet(data, sheetId, settings);
145-
}
146-
147-
if (!Array.isArray(data) || !data.length) {
148-
return setState(state => ({
149-
...state,
150-
error:
151-
'Configuration error: data is not in the correct format.',
152-
filtering: false,
153-
loading: false,
154-
}));
155-
}
156-
157-
if (timezone) {
158-
try {
159-
// check if timezone is valid
160-
Intl.DateTimeFormat(undefined, { timeZone: timezone });
161-
} catch (e) {
162-
return setState(state => ({
142+
if (!google) {
143+
setState(state => ({
163144
...state,
164-
error: `Timezone ${timezone} is not valid. Please use one like Europe/Rome.`,
145+
error: 'Configuration error: a Google API key is required.',
165146
filtering: false,
166147
loading: false,
167148
}));
168149
}
150+
src = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/A1:ZZ?key=${google}`;
151+
}
152+
153+
// cache busting
154+
if (src.endsWith('.json') && input.meeting) {
155+
src = `${src}?${new Date().getTime()}`;
156+
}
157+
158+
// fetch json data file and build indexes
159+
return fetch(src);
160+
})
161+
)
162+
.then(responses =>
163+
Promise.all(
164+
responses.map(res =>
165+
res.ok ? res.json() : Promise.reject(res.status)
166+
)
167+
)
168+
)
169+
.then((responses: JSONData[][]) => {
170+
const data = responses
171+
.map((data, index) => {
172+
const sheetId = sheets[index];
173+
return isGoogleSheetData(data) && sheetId
174+
? translateGoogleSheet(data, sheetId, settings)
175+
: data;
176+
})
177+
.flat();
178+
179+
if (!Array.isArray(data) || !data.length) {
180+
throw new Error(
181+
'Configuration error: data is not in the correct format.'
182+
);
169183
}
170184

171185
const [meetings, indexes, capabilities] = loadMeetingData(
@@ -177,12 +191,7 @@ export default function TsmlUI({
177191
);
178192

179193
if (!timezone && !Object.keys(meetings).length) {
180-
return setState(state => ({
181-
...state,
182-
error: 'Configuration error: time zone is not set.',
183-
filtering: false,
184-
loading: false,
185-
}));
194+
throw new Error('Configuration error: time zone is not set.');
186195
}
187196

188197
const waitingForGeo =
@@ -200,27 +209,10 @@ export default function TsmlUI({
200209
meetings,
201210
}));
202211
})
203-
.catch(error => {
204-
const errors = {
205-
400: 'bad request',
206-
401: 'unauthorized',
207-
403: 'forbidden',
208-
404: 'not found',
209-
429: 'too many requests',
210-
500: 'internal server',
211-
502: 'bad gateway',
212-
503: 'service unavailable',
213-
504: 'gateway timeout',
214-
};
212+
.catch((error: string | TypeError) => {
215213
setState(state => ({
216214
...state,
217-
error: errors[error as keyof typeof errors]
218-
? `Error: ${
219-
errors[error as keyof typeof errors]
220-
} (${error}) when ${
221-
sheetId ? 'contacting Google' : 'loading data'
222-
}.`
223-
: error.toString(),
215+
error: String(error),
224216
filtering: false,
225217
loading: false,
226218
}));

src/helpers/translate-google-sheet.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { DateTime } from 'luxon';
22

3-
import { en, es, fr, ja, sv, sk } from '../i18n';
3+
import { en, es, fr, ja, sk, sv } from '../i18n';
44

55
import { formatSlug } from './format-slug';
66

@@ -10,16 +10,22 @@ export type GoogleSheetData = {
1010
values: string[][];
1111
};
1212

13+
export const isGoogleSheetData = (data: any): data is GoogleSheetData => {
14+
return data && Array.isArray(data.values);
15+
};
16+
1317
// translates Google Sheet JSON into Meeting Guide format (example puget-sound.html)
1418
export function translateGoogleSheet(
1519
data: GoogleSheetData,
1620
sheetId: string,
1721
settings: TSMLReactConfig
1822
) {
19-
if (!data.values || !data.values.length) return;
20-
2123
const meetings: JSONData[] = [];
2224

25+
if (!data.values.length) {
26+
return [];
27+
}
28+
2329
// @ts-expect-error TODO
2430
const headers = data.values
2531
.shift()

test/__tests__/translate-google-sheet.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ describe('translateGoogleSheet', () => {
7878
);
7979
});
8080

81-
it('returns undefined when sheet is empty', () => {
81+
it('returns empty when sheet is empty', () => {
8282
expect(
8383
translateGoogleSheet({ values: [] }, sheetId, defaults)
84-
).toStrictEqual(undefined);
84+
).toStrictEqual([]);
8585
});
8686
});

0 commit comments

Comments
 (0)