Skip to content

Commit bca0ca1

Browse files
Feat: official feed in add feed form (#813)
* official feed in add feed form
1 parent 276fbe6 commit bca0ca1

File tree

9 files changed

+86
-13
lines changed

9 files changed

+86
-13
lines changed

functions/packages/feed-form/src/__tests__/feed-form.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {type FeedSubmissionFormRequestBody} from "../impl/types";
88
const sampleRequestBodyGTFS: FeedSubmissionFormRequestBody = {
99
name: "Sample Feed",
1010
isOfficialProducer: "yes",
11+
isOfficialFeed: "yes",
1112
dataType: "gtfs",
1213
transitProviderName: "Sample Transit Provider",
1314
feedLink: "https://example.com/feed",
@@ -137,6 +138,7 @@ describe("Feed Form Implementation", () => {
137138
[SheetCol.LinkToAssociatedGTFS]:
138139
sampleRequestBodyGTFS.gtfsRelatedScheduleLink,
139140
[SheetCol.LogoPermission]: sampleRequestBodyGTFS.hasLogoPermission,
141+
[SheetCol.OfficialFeedSource]: sampleRequestBodyGTFS.isOfficialFeed,
140142
});
141143
});
142144
});

functions/packages/feed-form/src/__tests__/github-issue-builder.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ describe("buildGithubIssueBody", () => {
77
it("should generate content for basic form data with GTFS feed", () => {
88
const formData: FeedSubmissionFormRequestBody = {
99
isOfficialProducer: "yes",
10+
isOfficialFeed: "yes",
1011
dataType: "gtfs",
1112
transitProviderName: "Test Agency",
1213
name: "Test Agency",
@@ -64,6 +65,7 @@ describe("buildGithubIssueBody", () => {
6465
it("should handle optional location fields gracefully", () => {
6566
const formData: FeedSubmissionFormRequestBody = {
6667
isOfficialProducer: "no",
68+
isOfficialFeed: "yes",
6769
dataType: "gtfs",
6870
transitProviderName: "Test Agency",
6971
name: "Test Agency",
@@ -111,6 +113,7 @@ describe("buildGithubIssueBody", () => {
111113
it("should handle non-GTFS data types (tu, vp, sa)", () => {
112114
const formData: FeedSubmissionFormRequestBody = {
113115
isOfficialProducer: "yes",
116+
isOfficialFeed: "yes",
114117
dataType: "gtfs_rt",
115118
transitProviderName: "Test Agency",
116119
name: "Test Agency",
@@ -159,6 +162,7 @@ describe("buildGithubIssueBody", () => {
159162
it("should handle missing authentication details", () => {
160163
const formData: FeedSubmissionFormRequestBody = {
161164
isOfficialProducer: "",
165+
isOfficialFeed: "yes",
162166
dataType: "gtfs",
163167
transitProviderName: "Test Agency",
164168
name: "Test Agency",

functions/packages/feed-form/src/impl/feed-form-impl.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ export const writeToSheet = async (
4343
process.env.GITHUB_TOKEN
4444
);
4545
}
46-
await sendSlackWebhook(sheetId, githubIssueUrl);
46+
await sendSlackWebhook(
47+
sheetId,
48+
githubIssueUrl,
49+
formData.isOfficialFeed === "yes"
50+
);
4751
return {message: "Data written to the new sheet successfully!"};
4852
} catch (error) {
4953
logger.error("Error writing to sheet:", error);
@@ -81,6 +85,7 @@ export enum SheetCol {
8185
UserInterview = "User interview email",
8286
DataProducerEmail = "Data producer email",
8387
OfficialProducer = "Are you the official producer or transit agency responsible for this data?",
88+
OfficialFeedSource = "Is Official Feed Source",
8489
ToolsAndSupport = "What tools and support do you use to create your GTFS data?",
8590
LinkToAssociatedGTFS = "Link to associated GTFS Schedule feed",
8691
LogoPermission = "Do we have permission to share your logo on https://mobilitydatabase.org/contribute?",
@@ -186,6 +191,7 @@ export function buildFeedRow(
186191
[SheetCol.UserInterview]: formData.userInterviewEmail ?? "",
187192
[SheetCol.DataProducerEmail]: formData.dataProducerEmail ?? "",
188193
[SheetCol.OfficialProducer]: formData.isOfficialProducer,
194+
[SheetCol.OfficialFeedSource]: formData.isOfficialFeed ?? "",
189195
[SheetCol.ToolsAndSupport]: formData.whatToolsUsedText ?? "",
190196
[SheetCol.LinkToAssociatedGTFS]: formData.gtfsRelatedScheduleLink ?? "",
191197
[SheetCol.LogoPermission]: formData.hasLogoPermission,
@@ -196,11 +202,20 @@ export function buildFeedRow(
196202
* Sends a Slack webhook message to the configured Slack webhook URL
197203
* @param {string} spreadsheetId The ID of the Google Sheet
198204
* @param {string} githubIssueUrl The URL of the created GitHub issue
205+
* @param {boolean} isOfficialSource Whether the feed is an official source
199206
*/
200-
async function sendSlackWebhook(spreadsheetId: string, githubIssueUrl: string) {
207+
async function sendSlackWebhook(
208+
spreadsheetId: string,
209+
githubIssueUrl: string,
210+
isOfficialSource: boolean
211+
) {
201212
const slackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
202213
const sheetUrl = `https://docs.google.com/spreadsheets/d/${spreadsheetId}/edit`;
203214
if (slackWebhookUrl !== undefined && slackWebhookUrl !== "") {
215+
let headerText = "New Feed Added";
216+
if (isOfficialSource) {
217+
headerText += " 🔹 Official Source";
218+
}
204219
const linksElement = [
205220
{
206221
type: "emoji",
@@ -237,7 +252,8 @@ async function sendSlackWebhook(spreadsheetId: string, githubIssueUrl: string) {
237252
type: "header",
238253
text: {
239254
type: "plain_text",
240-
text: "New Feed Added",
255+
text: headerText,
256+
emoji: true,
241257
},
242258
},
243259
{
@@ -259,11 +275,11 @@ async function sendSlackWebhook(spreadsheetId: string, githubIssueUrl: string) {
259275
],
260276
},
261277
{
262-
"type": "rich_text",
263-
"elements": [
278+
type: "rich_text",
279+
elements: [
264280
{
265-
"type": "rich_text_section",
266-
"elements": linksElement,
281+
type: "rich_text_section",
282+
elements: linksElement,
267283
},
268284
],
269285
},
@@ -292,9 +308,12 @@ async function createGithubIssue(
292308
): Promise<string> {
293309
const githubRepoUrlIssue =
294310
"https://api.github.com/repos/MobilityData/mobility-database-catalogs/issues";
295-
const issueTitle =
311+
let issueTitle =
296312
"New Feed Added" +
297313
(formData.transitProviderName ? `: ${formData.transitProviderName}` : "");
314+
if (formData.isOfficialFeed === "yes") {
315+
issueTitle += " - Official Feed";
316+
}
298317
const issueBody = buildGithubIssueBody(formData, spreadsheetId);
299318
try {
300319
const response = await axios.post(
@@ -353,7 +372,7 @@ export function buildGithubIssueBody(
353372
${formData.isUpdatingFeed === "yes" ? "Feed update" : "New feed"}`;
354373

355374
if (formData.name) {
356-
content += `
375+
content += `
357376
358377
#### Name
359378
${formData.name}`;

functions/packages/feed-form/src/impl/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type AuthTypes =
77

88
export interface FeedSubmissionFormRequestBody {
99
isOfficialProducer: YesNoFormInput;
10+
isOfficialFeed: "yes" | "no" | "unsure" | undefined;
1011
dataType: "gtfs" | "gtfs_rt";
1112
transitProviderName?: string;
1213
feedLink?: string;

web-app/cypress/e2e/addFeedForm.cy.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe('Add Feed Form', () => {
2323
cy.get('[data-cy=isOfficialProducerYes]').click({
2424
force: true,
2525
});
26+
cy.muiDropdownSelect('[data-cy=isOfficialFeed]', 'yes');
2627
cy.get('[data-cy=feedLink] input').type('https://example.com/feed', {
2728
force: true,
2829
});
@@ -49,6 +50,7 @@ describe('Add Feed Form', () => {
4950

5051
it('should submit a new gtfs realtime feed as not official producer', () => {
5152
cy.get('[data-cy=isOfficialProducerNo]').click();
53+
cy.muiDropdownSelect('[data-cy=isOfficialFeed]', 'no');
5254
cy.muiDropdownSelect('[data-cy=dataType]', 'gtfs_rt');
5355
cy.get('[data-cy=submitFirstStep]').click();
5456
cy.url().should('include', '/contribute?step=2');
@@ -70,12 +72,14 @@ describe('Add Feed Form', () => {
7072
it('should display errors for gtfs feed', () => {
7173
cy.muiDropdownSelect('[data-cy=isUpdatingFeed]', 'yes');
7274
cy.get('[data-cy=submitFirstStep]').click();
75+
cy.assetMuiError('[data-cy=isOfficialFeedLabel]');
7376
cy.assetMuiError('[data-cy=isOfficialProducerLabel]');
7477
cy.assetMuiError('[data-cy=feedLinkLabel]');
7578
cy.assetMuiError('[data-cy=oldFeedLabel]');
7679
cy.location('pathname').should('eq', '/contribute');
7780
// Step 1 values
7881
cy.get('[data-cy=isOfficialProducerYes]').click();
82+
cy.muiDropdownSelect('[data-cy=isOfficialFeed]', 'yes');
7983
cy.get('[data-cy=feedLink] input').type('https://example.com/feed', {
8084
force: true,
8185
});
@@ -106,9 +110,11 @@ describe('Add Feed Form', () => {
106110
cy.muiDropdownSelect('[data-cy=dataType]', 'gtfs_rt');
107111
cy.get('[data-cy=submitFirstStep]').click();
108112
cy.assetMuiError('[data-cy=isOfficialProducerLabel]');
113+
cy.assetMuiError('[data-cy=isOfficialFeedLabel]');
109114
cy.location('pathname').should('eq', '/contribute');
110115
// Step 1 values
111116
cy.get('[data-cy=isOfficialProducerYes]').click();
117+
cy.muiDropdownSelect('[data-cy=isOfficialFeed]', 'yes');
112118
cy.muiDropdownSelect('[data-cy=isUpdatingFeed]', 'yes');
113119
cy.get('[data-cy=submitFirstStep]').click();
114120
// Step 2

web-app/public/locales/en/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"no": "No",
3131
"required": "This field is required",
3232
"submit": "Submit",
33-
"select": "Select"
33+
"select": "Select",
34+
"notSure": "Not sure"
3435
},
3536
"country": "Country",
3637
"chooseCountry": "Choose a country",

web-app/public/locales/en/feeds.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"signUpAction": "Sign up for an account",
2525
"loginSuccess": "You were successfully logged in, you can now add or update a feed.",
2626
"dataTypeRequired": "Data format required",
27+
"isOfficialFeedRequired": "Official feed required",
2728
"feedLinkRequired": "Feed link required",
2829
"oldFeedLinkRequired": "Old feed link required",
2930
"dataProducerEmailRequired": "Data producer email required",
@@ -59,6 +60,8 @@
5960
"boundingBoxTitle": "Bounding box from stops.txt",
6061
"unableToGenerateBoundingBox": "Unable to generate bounding box.",
6162
"areYouOfficialProducer": "Are you the official producer or transit agency responsible for this data ?",
63+
"isOfficialSource": "Is this an official data source?",
64+
"isOfficialSourceDetails": "Select \"Yes\" if the inputted feed is the official source of information by the transit provider and should be used to display to riders.",
6265
"feedLink": "Feed Link",
6366
"areYouUpdatingFeed": "Are you updating a feed?",
6467
"oldFeedLink": "Old Feed Link",

web-app/src/app/screens/FeedSubmission/Form/FirstStep.tsx

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ import { type YesNoFormInput, type FeedSubmissionFormFormInput } from '.';
2222
import { useEffect } from 'react';
2323
import { useTranslation } from 'react-i18next';
2424
import { isValidFeedLink } from '../../../services/feeds/utils';
25+
import FormLabelDescription from './components/FormLabelDescription';
2526

2627
export interface FeedSubmissionFormFormInputFirstStep {
2728
isOfficialProducer: YesNoFormInput;
29+
isOfficialFeed: 'yes' | 'no' | 'unsure' | undefined;
2830
dataType: 'gtfs' | 'gtfs_rt';
2931
transitProviderName?: string;
3032
feedLink?: string;
@@ -51,6 +53,7 @@ export default function FormFirstStep({
5153
} = useForm<FeedSubmissionFormFormInputFirstStep>({
5254
defaultValues: {
5355
isOfficialProducer: initialValues.isOfficialProducer,
56+
isOfficialFeed: initialValues.isOfficialFeed,
5457
dataType: initialValues.dataType,
5558
transitProviderName: initialValues.transitProviderName,
5659
feedLink: initialValues.feedLink,
@@ -136,15 +139,47 @@ export default function FormFirstStep({
136139
<Grid item sx={{ '&.MuiGrid-item': { pt: '4px' } }}>
137140
<FormControl
138141
component='fieldset'
139-
error={errors.dataType !== undefined}
142+
error={errors.isOfficialFeed !== undefined}
140143
>
144+
<FormLabel required data-cy='isOfficialFeedLabel'>
145+
{t('isOfficialSource')}
146+
</FormLabel>
147+
<FormLabelDescription>
148+
{t('isOfficialSourceDetails')}
149+
</FormLabelDescription>
150+
<Controller
151+
rules={{ required: t('form.isOfficialFeedRequired') }}
152+
control={control}
153+
name='isOfficialFeed'
154+
render={({ field }) => (
155+
<>
156+
<Select
157+
{...field}
158+
data-cy='isOfficialFeed'
159+
sx={{ width: '200px' }}
160+
>
161+
<MenuItem value={'yes'}>{t('common:form.yes')}</MenuItem>
162+
<MenuItem value={'no'}>{t('common:form.no')}</MenuItem>
163+
<MenuItem value={'unsure'}>
164+
{t('common:form.notSure')}
165+
</MenuItem>
166+
</Select>
167+
<FormHelperText>
168+
{errors.isOfficialFeed?.message ?? ''}
169+
</FormHelperText>
170+
</>
171+
)}
172+
/>
173+
</FormControl>
174+
</Grid>
175+
<Grid item>
176+
<FormControl component='fieldset'>
141177
<FormLabel required>{t('dataType')}</FormLabel>
142178
<Controller
143-
rules={{ required: t('dataTypeRequired') }}
144179
control={control}
145180
name='dataType'
146181
render={({ field }) => (
147-
<Select {...field} data-cy='dataType'>
182+
<Select {...field} data-cy='dataType' sx={{ width: '200px' }}>
148183
<MenuItem value={'gtfs'}>
149184
{t('common:gtfsSchedule')}
150185
</MenuItem>

web-app/src/app/screens/FeedSubmission/Form/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export type AuthTypes =
2525

2626
export interface FeedSubmissionFormFormInput {
2727
isOfficialProducer: YesNoFormInput;
28+
isOfficialFeed: 'yes' | 'no' | 'unsure' | undefined;
2829
dataType: 'gtfs' | 'gtfs_rt';
2930
transitProviderName: string;
3031
feedLink?: string;
@@ -54,6 +55,7 @@ export interface FeedSubmissionFormFormInput {
5455

5556
const defaultFormValues: FeedSubmissionFormFormInput = {
5657
isOfficialProducer: '',
58+
isOfficialFeed: undefined,
5759
dataType: 'gtfs',
5860
transitProviderName: '',
5961
feedLink: '',

0 commit comments

Comments
 (0)