Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@
"isAuthRequiredDetails": " Select \"Yes\" if a user has to login or provide credentials to download the feed",
"detailPageDescription": "Explore the {formattedName} {dataTypeVerbose} feed details with access to a quality data insights",
"mapPageDescription": "Explore the {formattedName} {dataTypeVerbose} feed on an interactive map showing routes, stops, and transit coverage.",
"sealPageDescription": "See how the {formattedName} {dataTypeVerbose} feed performs against each Seal of Reliability criterion.",
"officialFeed": "Official Feed",
"officialFeedTooltip": "The transit provider has confirmed this feed should be shared with riders. This has been confirmed either by the transit provider providing the feed on their website or from personalized confirmation with the Mobility Database team.",
"officialFeedTooltipShort": "Verified feed: Confirmed by the transit provider or the Mobility Database team for rider use.",
Expand Down
1 change: 1 addition & 0 deletions messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@
"isAuthRequiredDetails": " Select \"Yes\" if a user has to login or provide credentials to download the feed",
"detailPageDescription": "Explore the {formattedName} {dataTypeVerbose} feed details with access to a quality data insights",
"mapPageDescription": "Explore the {formattedName} {dataTypeVerbose} feed on an interactive map showing routes, stops, and transit coverage.",
"sealPageDescription": "Découvrez comment le flux {dataTypeVerbose} de {formattedName} se comporte face à chaque critère du Sceau de fiabilité.",
"officialFeed": "Official Feed",
"officialFeedTooltip": "The transit provider has confirmed this feed should be shared with riders. This has been confirmed either by the transit provider providing the feed on their website or from personalized confirmation with the Mobility Database team.",
"officialFeedTooltipShort": "Verified feed: Confirmed by the transit provider or the Mobility Database team for rider use.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { fetchCompleteFeedData } from '../../lib/feed-data';
import { fetchAuthedSealAnalysisData } from '../../lib/seal-analysis-data';
import { getLatestDataset } from '../../../../../../screens/Feed/Feed.functions';
import { notFound } from 'next/navigation';
import type { Metadata, ResolvingMetadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { generateSealFeedMetadata } from '../../lib/generate-feed-metadata';

interface Props {
params: Promise<{ feedDataType: string; feedId: string }>;
params: Promise<{ locale: string; feedDataType: string; feedId: string }>;
}

/**
Expand All @@ -15,6 +18,22 @@ interface Props {
*/
export const dynamic = 'force-dynamic';

export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata,
): Promise<Metadata> {
const { locale, feedId, feedDataType } = await params;
const t = await getTranslations({ locale });

// Same cache as the page component - no extra API call.
const feedData = await fetchCompleteFeedData(feedDataType, feedId);

return generateSealFeedMetadata({
feed: feedData?.feed,
t,
});
}

export default async function AuthedFeedReliabilityPage({
params,
}: Props): Promise<ReactElement> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
generateDescriptionMetaTag,
generateMapPageTitle,
generateMapDescriptionMetaTag,
generateSealPageTitle,
generateSealDescriptionMetaTag,
} from '../../../../../screens/Feed/Feed.functions';

/**
Expand Down Expand Up @@ -394,3 +396,54 @@ export function generateMapFeedMetadata({
},
};
}

/**
* Shared metadata generation logic for feed Seal of Reliability pages
* (authed and static).
*
* @param feed - The feed data
* @param t - Translation function
*/
export function generateSealFeedMetadata({
Comment thread
cka-y marked this conversation as resolved.
feed,
t,
}: GenerateFeedMetadataParams): Metadata {
if (feed == null) {
return {
title: 'Feed Not Found | Mobility Database',
};
}
const feedDataType = feed.data_type;
const feedId = feed.id;
const sortedProviders = formatProvidersSorted(feed?.provider ?? '');
const title = generateSealPageTitle(
sortedProviders,
(feed as { feed_name?: string })?.feed_name,
);
const description = generateSealDescriptionMetaTag(
t,
sortedProviders,
(feed as { feed_name?: string })?.feed_name,
);
const url = `https://mobilitydatabase.org/feeds/${feedDataType}/${feedId}/seal-of-reliability`;

return {
title,
description,
openGraph: {
title,
description,
url,
siteName: 'Mobility Database',
type: 'website',
},
twitter: {
card: 'summary',
title,
description,
},
alternates: {
canonical: url,
},
};
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import FeedReliabilityView from '../../../../../../screens/Feed/components/FeedReliabilityView';
import { type ReactElement } from 'react';
import { notFound } from 'next/navigation';
import type { Metadata, ResolvingMetadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { fetchGuestFeedData } from '../../lib/guest-feed-data';
import { generateSealFeedMetadata } from '../../lib/generate-feed-metadata';
import { fetchGuestSealAnalysisData } from '../../lib/seal-analysis-data';
import { getLatestDataset } from '../../../../../../screens/Feed/Feed.functions';

Expand All @@ -22,6 +25,23 @@ interface Props {
*/
export const dynamic = 'force-dynamic';

export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata,
): Promise<Metadata> {
const { feedId, feedDataType } = await params;

const [t, feedData] = await Promise.all([
getTranslations(),
fetchGuestFeedData(feedDataType, feedId),
]);

return generateSealFeedMetadata({
feed: feedData.feed,
t,
});
}

/**
* Seal of Reliability breakdown page (GUEST version).
*
Expand Down
39 changes: 39 additions & 0 deletions src/app/screens/Feed/Feed.functions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,45 @@ export function generateMapDescriptionMetaTag(
return t('feeds.mapPageDescription', { formattedName, dataTypeVerbose });
}

/**
* The Seal of Reliability only exists for GTFS Schedule feeds - both seal
* pages throw for any other data type - so there is no data type to branch on.
*/
export function generateSealPageTitle(
sortedProviders: string[],
feedName?: string,
): string {
let newDocTitle = getFeedFormattedName(sortedProviders, feedName);

if (newDocTitle !== '') {
newDocTitle += ' GTFS Schedule Feed Seal of Reliability - ';
}

newDocTitle += 'Mobility Database';
return newDocTitle;
}

/**
* See generateSealPageTitle - GTFS Schedule is the only applicable data type.
*/
export function generateSealDescriptionMetaTag(
t: (key: string, options?: Record<string, string>) => string,
sortedProviders: string[],
feedName?: string,
): string {
const formattedName = getFeedFormattedName(sortedProviders, feedName);
if (
sortedProviders.length === 0 &&
(feedName === undefined || feedName === '')
) {
return '';
}
return t('feeds.sealPageDescription', {
formattedName,
dataTypeVerbose: t('common.gtfsSchedule'),
});
}

export const formatServiceDateRange = (
dateStart: string,
dateEnd: string,
Expand Down
14 changes: 14 additions & 0 deletions src/app/screens/Feed/Feed.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
generatePageTitle,
generateDescriptionMetaTag,
generateMapPageTitle,
generateSealPageTitle,
generateMapDescriptionMetaTag,
} from './Feed.functions';
import FeedTitle from './components/FeedTitle';
Expand Down Expand Up @@ -298,6 +299,19 @@ describe('Feed page', () => {
);
});

it('should generate the correct seal of reliability page title', () => {
const titleAllInfo = generateSealPageTitle(
['Department of Transport', 'Public Transport'],
'Darwin public bus network',
);
expect(titleAllInfo).toEqual(
'Department of Transport, Darwin public bus network GTFS Schedule Feed Seal of Reliability - Mobility Database',
);

const titleAllEmpty = generateSealPageTitle([], '');
expect(titleAllEmpty).toEqual('Mobility Database');
});

it('should generate the correct map page description', () => {
const mockT = jest.fn((key, params) => {
switch (key) {
Expand Down
Loading