Skip to content

Commit d6ca0a3

Browse files
Feat: gbfs validator, validation report UI styling + gbfs java validator types (#1422)
* inclusion of gbfs openapi types * gbfs validation report styling * accessibility improvements * Theme contrast text for dark mode updated * validation styles file naming update
1 parent 45c9363 commit d6ca0a3

File tree

13 files changed

+6685
-302
lines changed

13 files changed

+6685
-302
lines changed

.github/workflows/typescript-generator-check.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,29 @@ jobs:
4444
run: yarn generate:api-types:output
4545
env:
4646
OUTPUT_PATH_TYPES: src/app/services/feeds/generated/types.ts
47+
48+
- name: Generate TypeScript gbfs validator types
49+
working-directory: web-app
50+
run: yarn generate:gbfs-validator-types:output
51+
env:
52+
OUTPUT_PATH_TYPES: src/app/services/feeds/generated/gbfs-validator-types.ts
4753

4854
- name: Upload generated types
4955
uses: actions/upload-artifact@v4
5056
with:
5157
name: generated_types.ts
5258
path: web-app/src/app/services/feeds/generated/types.ts
59+
60+
- name: Upload generated gbfs types
61+
uses: actions/upload-artifact@v4
62+
with:
63+
name: generated_types.ts
64+
path: web-app/src/app/services/feeds/generated/gbfs-validator-types.ts
5365

5466
- name: Compare TypeScript types with existing types
5567
working-directory: web-app
5668
run: diff src/app/services/feeds/generated/types.ts src/app/services/feeds/types.ts || (echo "Types are different!" && exit 1)
69+
70+
- name: Compare gbfs validator TypeScript types with existing types
71+
working-directory: web-app
72+
run: diff src/app/services/feeds/generated/gbfs-validator-types.ts src/app/services/feeds/gbfs-validator-types.ts || (echo "Gbfs Validator Types are different!" && exit 1)

docs/GbfsValidator.yaml

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
openapi: 3.0.0
2+
info:
3+
title: GBFS Validator API
4+
version: "0.1"
5+
paths:
6+
/validate:
7+
post:
8+
summary: Validate GBFS feed
9+
requestBody:
10+
$ref: '#/components/requestBodies/ValidateRequestBody'
11+
responses:
12+
'200':
13+
description: Validation result
14+
content:
15+
application/json:
16+
schema:
17+
$ref: "#/components/schemas/ValidationResult"
18+
19+
components:
20+
requestBodies:
21+
ValidateRequestBody:
22+
required: true
23+
content:
24+
application/json:
25+
schema:
26+
type: object
27+
required:
28+
- feedUrl
29+
properties:
30+
feedUrl:
31+
type: string
32+
example: "https://example.com/gbfs.json"
33+
auth:
34+
oneOf:
35+
- $ref: '#/components/schemas/BasicAuth'
36+
- $ref: '#/components/schemas/BearerTokenAuth'
37+
- $ref: '#/components/schemas/OAuthClientCredentialsGrantAuth'
38+
discriminator:
39+
propertyName: authType
40+
mapping:
41+
basicAuth: '#/components/schemas/BasicAuth'
42+
bearerToken: '#/components/schemas/BearerTokenAuth'
43+
oauthClientCredentialsGrant: '#/components/schemas/OAuthClientCredentialsGrantAuth'
44+
45+
schemas:
46+
BasicAuth:
47+
type: object
48+
properties:
49+
authType:
50+
type: string
51+
example: "BasicAuth"
52+
username:
53+
type: string
54+
example: username
55+
password:
56+
type: string
57+
example: password
58+
59+
BearerTokenAuth:
60+
type: object
61+
properties:
62+
authType:
63+
type: string
64+
example: bearerToken
65+
token:
66+
type: string
67+
example: "THE_TOKEN"
68+
69+
OAuthClientCredentialsGrantAuth:
70+
type: object
71+
properties:
72+
authType:
73+
type: string
74+
example: "OAuthClientCredentialsGrantAuth"
75+
clientId:
76+
type: string
77+
example: "CLIENT_ID"
78+
clientSecret:
79+
type: string
80+
example: "THE_SECRET"
81+
tokenUrl:
82+
type: string
83+
example: "https://token.example.com"
84+
85+
FileError:
86+
type: object
87+
properties:
88+
keyword:
89+
type: string
90+
description: |
91+
Possible known keywords: type, enum, format, required, minimum, maximum, pattern.
92+
instancePath:
93+
type: string
94+
description: |
95+
The JSON Pointer path to the part of the instance that failed validation.
96+
schemaPath:
97+
type: string
98+
description: |
99+
The JSON Pointer path to the part of the schema that triggered the error.
100+
message:
101+
type: string
102+
description: |
103+
Human-readable message describing the error.
104+
required:
105+
- keyword
106+
- instancePath
107+
- schemaPath
108+
- message
109+
110+
SystemError:
111+
type: object
112+
properties:
113+
error:
114+
type: string
115+
description: "Error code or identifier for the system error. Examples: HTTP_ERROR_404, PARSE_ERROR, CONNECTION_ERROR, FILE_NOT_FOUND."
116+
message:
117+
type: string
118+
description: "Human-readable message describing the system error."
119+
required:
120+
- error
121+
- message
122+
123+
GbfsFile:
124+
type: object
125+
properties:
126+
name:
127+
type: string
128+
description: |
129+
Key identifying the type of feed this is. The key MUST be the base file name defined in the spec for the corresponding feed type ( system_information for system_information.json file, station_information for station_information.json file).
130+
example: gbfs
131+
url:
132+
type: string
133+
format: url
134+
example: https://www.example.com/gbfs/v3/gbfs.json
135+
version:
136+
type: string
137+
example: "3.0"
138+
language:
139+
type: string
140+
nullable: true
141+
example: "en"
142+
description: "Only relevant for pre-v3 files"
143+
errors:
144+
type: array
145+
items:
146+
$ref: "#/components/schemas/FileError"
147+
schema:
148+
type: object
149+
systemErrors:
150+
type: array
151+
items:
152+
$ref: "#/components/schemas/SystemError"
153+
description: "System errors that occurred while processing this file, such as fetch failures or parsing errors. These are not validation errors but rather issues that prevented proper validation."
154+
155+
ValidationResult:
156+
type: object
157+
properties:
158+
summary:
159+
type: object
160+
properties:
161+
validatorVersion:
162+
type: string
163+
example: "v1.2"
164+
files:
165+
type: array
166+
items:
167+
$ref: "#/components/schemas/GbfsFile"

web-app/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@
7373
"cypress:open": "cypress open",
7474
"firebase:auth:emulator:dev": "firebase emulators:start --only auth --project mobility-feeds-dev",
7575
"generate:api-types:output": "npx openapi-typescript ../docs/DatabaseCatalogAPI.yaml -o $OUTPUT_PATH_TYPES && eslint $OUTPUT_PATH_TYPES --fix",
76-
"generate:api-types": "OUTPUT_PATH_TYPES=src/app/services/feeds/types.ts npm run generate:api-types:output"
76+
"generate:api-types": "OUTPUT_PATH_TYPES=src/app/services/feeds/types.ts npm run generate:api-types:output",
77+
"generate:gbfs-validator-types:output": "npx openapi-typescript ../docs/GbfsValidator.yaml -o $OUTPUT_PATH_TYPES && eslint $OUTPUT_PATH_TYPES --fix",
78+
"generate:gbfs-validator-types": "OUTPUT_PATH_TYPES=src/app/services/feeds/gbfs-validator-types.ts npm run generate:gbfs-validator-types:output"
7779
},
7880
"eslintConfig": {
7981
"extends": [

web-app/src/app/Theme.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const darkPalette = {
8585
main: '#96a1ff',
8686
dark: '#4a5dff',
8787
light: '#e7e8ff',
88-
contrastText: '#E3E3E3',
88+
contrastText: '#1D1717',
8989
},
9090
secondary: {
9191
main: '#3959fa',

web-app/src/app/screens/Feed/Feed.styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const ctaContainerStyle: SxProps<Theme> = (theme) => ({
2828
});
2929

3030
export const featureChipsStyle: SxProps<Theme> = (theme) => ({
31-
color: theme.palette.primary.contrastText,
31+
color: theme.palette.secondary.contrastText,
3232
backgroundColor: theme.palette.secondary.dark,
3333
border: `2px solid transparent`,
3434
':hover': {

web-app/src/app/screens/Feed/components/GbfsVersions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export default function GbfsVersions({
141141
label={t('gbfsVersionsJson')}
142142
sx={{
143143
backgroundColor: theme.palette.primary.dark,
144-
color: theme.palette.primary.contrastText,
144+
color: theme.palette.secondary.contrastText,
145145
}}
146146
variant='filled'
147147
></Chip>

web-app/src/app/screens/Feeds/AdvancedSearchTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const renderGBFSDetails = (
108108
border: selectedGbfsVersions.includes('v' + version)
109109
? `2px solid ${theme.palette.primary.main}`
110110
: '',
111-
color: 'black',
111+
color: theme.palette.text.primary,
112112
}}
113113
/>
114114
))}

web-app/src/app/screens/GbfsValidator/GbfsFeedSearchInput.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ export default function GbfsFeedSearchInput(): React.ReactElement {
8282
// navigate to /gbfs-validator?AutoDiscoveryUrl=url&auth details
8383
// store the auth details in context
8484
// let the GbfsValidator component handle the loading state
85-
console.log('Validating GBFS Feed for URL: ', autoDiscoveryUrlInput);
8685
// I'm sure if a query param exists, instead of navigation, we will update the param, and have a useEffect to call the new feed to validate
8786
navigate(
8887
`/gbfs-validator?AutoDiscoveryUrl=${encodeURIComponent(

web-app/src/app/screens/GbfsValidator/validator.styles.ts renamed to web-app/src/app/screens/GbfsValidator/ValidationReport.styles.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { Box, styled } from '@mui/material';
1+
import {
2+
Box,
3+
styled,
4+
Typography,
5+
type SxProps,
6+
type Theme,
7+
} from '@mui/material';
8+
import { type CSSProperties } from 'react';
29

310
export const gbfsValidatorHeroBg = {
411
backgroundColor: '#43e0ff',
@@ -34,3 +41,49 @@ export const PromotionTextColumn = styled(Box)(({ theme }) => ({
3441
textAlign: 'center',
3542
},
3643
}));
44+
45+
export const ValidationReportTableStyles: SxProps<Theme> = (theme) => ({
46+
backgroundColor: theme.palette.background.paper,
47+
height: 'fit-content',
48+
position: 'sticky',
49+
top: theme.spacing(10),
50+
alignSelf: 'flex-start',
51+
width: '300px',
52+
minWidth: '300px',
53+
borderRadius: 1,
54+
overflow: 'visible',
55+
maxHeight: 'calc(100vh - 80px)',
56+
display: { xs: 'none', md: 'block' },
57+
});
58+
59+
export const ValidationElementCardStyles = (
60+
theme: Theme,
61+
index: number,
62+
): SxProps<Theme> => ({
63+
mx: 2,
64+
mb: 2,
65+
display: 'block',
66+
textDecoration: 'none',
67+
bgcolor: 'background.default',
68+
transition: 'box-shadow 0.3s ease',
69+
mt: index === 0 ? 0.5 : 0,
70+
'&:focus': {
71+
boxShadow: `0 0 0 3px ${theme.palette.primary.main}`,
72+
},
73+
});
74+
75+
export const ValidationErrorPathStyles = (theme: Theme): CSSProperties => ({
76+
padding: theme.spacing(0.5),
77+
background: theme.palette.background.paper,
78+
width: '100%',
79+
overflowX: 'auto',
80+
fontSize: '0.875em',
81+
});
82+
83+
export const ContentTitle = styled(Typography)(({ theme }) => ({
84+
color: theme.palette.text.secondary,
85+
fontSize: theme.typography.subtitle2.fontSize,
86+
padding: `0 ${theme.spacing(2)}`,
87+
lineHeight: '48px',
88+
fontWeight: 500,
89+
}));

0 commit comments

Comments
 (0)