Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DT-6647 Use new HSL route timetable API #5259

Merged
merged 2 commits into from
Feb 10, 2025
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
17 changes: 13 additions & 4 deletions app/component/routepage/ScheduleContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import { createFragmentContainer, graphql } from 'react-relay';
import { connectToStores } from 'fluxible-addons-react';
import { matchShape, routerShape, RedirectException } from 'found';
import moment from 'moment';
import { intlShape } from 'react-intl';
Expand Down Expand Up @@ -356,10 +357,12 @@ class ScheduleContainer extends PureComponent {
breakpoint: PropTypes.string.isRequired,
router: routerShape.isRequired,
route: routeShape.isRequired,
lang: PropTypes.string,
};

static defaultProps = {
serviceDay: undefined,
lang: 'en',
};

static contextTypes = {
Expand Down Expand Up @@ -900,11 +903,11 @@ class ScheduleContainer extends PureComponent {
const routeTimetableUrl =
routeTimetableHandler &&
this.context.config.URL.ROUTE_TIMETABLES[routeIdSplitted[0]] &&
routeTimetableHandler.timetableUrlResolver(
routeTimetableHandler.routeTimetableUrlResolver(
this.context.config.URL.ROUTE_TIMETABLES[routeIdSplitted[0]],
this.props.route,
this.context.config.API_SUBSCRIPTION_QUERY_PARAMETER_NAME,
this.context.config.API_SUBSCRIPTION_TOKEN,
newServiceDay,
this.props.lang,
);

const showTrips = this.getTrips(
Expand Down Expand Up @@ -1033,7 +1036,13 @@ class ScheduleContainer extends PureComponent {
}

const containerComponent = createFragmentContainer(
withBreakpoint(ScheduleContainer),
connectToStores(
withBreakpoint(ScheduleContainer),
['PreferencesStore'],
context => ({
lang: context.getStore('PreferencesStore').getLanguage(),
}),
),
{
pattern: graphql`
fragment ScheduleContainer_pattern on Pattern {
Expand Down
2 changes: 1 addition & 1 deletion app/component/stop/Timetable.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ class Timetable extends React.Component {
this.context.config.URL.STOP_TIMETABLES[stopIdSplitted[0]] &&
locationType !== 'STATION' &&
date
? stopTimetableHandler.stopPdfUrlResolver(
? stopTimetableHandler.stopTimetableUrlResolver(
this.context.config.URL.STOP_TIMETABLES[stopIdSplitted[0]],
this.props.stop,
date,
Expand Down
14 changes: 5 additions & 9 deletions app/configurations/config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const MAP_URL = process.env.MAP_URL || 'https://dev-cdn.digitransit.fi';
const MAP_VERSION = process.env.MAP_VERSION || 'v3';
const POI_MAP_PREFIX = `${MAP_URL}/map/v3/finland`;
const OTP_URL = process.env.OTP_URL || `${API_URL}/routing/v2/finland/`;
const STOP_TIMETABLES_URL =
process.env.STOP_TIMETABLES_URL || 'https://dev.kartat.hsl.fi';
const HSL_TIMETABLES_URL =
process.env.HSL_TIMETABLES_URL || 'https://dev.kartat.hsl.fi';
const APP_PATH = process.env.APP_CONTEXT || '';
const {
// AXE,
Expand Down Expand Up @@ -92,11 +92,11 @@ export default {
: ''
}`,
ROUTE_TIMETABLES: {
HSL: `${API_URL}/timetables/v1/hsl/routes/`,
tampere: 'https://www.nysse.fi/aikataulut-ja-reitit/linjat/',
HSL: `${HSL_TIMETABLES_URL}/julkaisin-render/?component=LineTimetable`,
tampere: 'https://www.nysse.fi/matkan-suunnittelu/linjat/',
},
STOP_TIMETABLES: {
HSL: `${STOP_TIMETABLES_URL}/julkaisin-render/?component=Timetable`,
HSL: `${HSL_TIMETABLES_URL}/julkaisin-render/?component=Timetable`,
},
WEATHER_DATA:
'https://opendata.fmi.fi/wfs?service=WFS&version=2.0.0&request=getFeature&storedquery_id=fmi::forecast::harmonie::surface::point::simple&timestep=5&parameters=temperature,WindSpeedMS,WeatherSymbol3',
Expand Down Expand Up @@ -559,10 +559,6 @@ export default {

defaultMapZoom: 12,

availableRouteTimetables: {},

routeTimetableUrlResolver: {},

showTenWeeksOnRouteSchedule: true,

useRealtimeTravellerCapacities: false,
Expand Down
3 changes: 0 additions & 3 deletions app/configurations/config.matka.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ const virtualMonitorBaseUrl = IS_DEV
? 'https://dev-matkamonitori.digitransit.fi'
: 'https://matkamonitori.digitransit.fi';

// route timetable data needs to be up-to-date before this is enabled
// const HSLRouteTimetable = require('./timetableConfigUtils').default.HSLRoutes;

export default {
CONFIG,
OTPTimeout: process.env.OTP_TIMEOUT || 30000,
Expand Down
59 changes: 28 additions & 31 deletions app/configurations/timetableConfigUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,33 @@

export default {
HSL: {
// Gets updated when server starts with {routeName: timetableName}
// where routeName and timetableNames are route gtfsId values without "<feedname>:"
availableRouteTimetables: {},

// gets the name of the route (in gtfsId format without the "<feedname>:" part) which
// contains the timetable pdf for the current route (it can be stored under different route)
// if there is no available timetable for the route, return null so that the weekly
// timetable button will not be rendered in UI
timetableUrlResolver: function timetableUrlResolver(
routeTimetableUrlResolver: function routeTimetableUrlResolver(
baseURL,
route,
subscriptionParam,
subscriptionToken,
date,
lang,
) {
const routeIdSplitted = route.gtfsId.split(':');
const routeId = routeIdSplitted[1];
const routePDFUrlName = this.availableRouteTimetables[routeId];
if (routePDFUrlName === undefined) {
return null;
}
const routeId = route.gtfsId.split(':')[1];

// From YYYYMMDD to YYYY-MM-DD
const formattedDate = date.replace(/(\d{4})(\d{2})(\d{2})/g, '$1-$2-$3');

const url = new URL(`${baseURL}${routePDFUrlName}.pdf`);
if (subscriptionParam && subscriptionToken) {
url.searchParams.set(subscriptionParam, subscriptionToken);
}
const defaultSearchParams =
'props[showPrintButton]=true&props[redirect]=false';
const url = new URL(`${baseURL}&${defaultSearchParams}`);
url.searchParams.append('props[lineId]', routeId);
url.searchParams.append('props[dateBegin]', formattedDate);
url.searchParams.append('props[dateEnd]', formattedDate);
url.searchParams.append('props[lang]', lang);
return url;
},
setAvailableRouteTimetables: function setAvailableRouteTimetables(
timetables,
stopTimetableUrlResolver: function stopTimetableUrlResolver(
baseURL,
stop,
date,
lang,
) {
this.availableRouteTimetables = timetables;
},
stopPdfUrlResolver: function stopPdfUrlResolver(baseURL, stop, date, lang) {
const stopId = stop.gtfsId.split(':')[1];
// From YYYYMMDD to YYYY-MM-DD
const formattedDate = date.replace(/(\d{4})(\d{2})(\d{2})/g, '$1-$2-$3');
const defaultSearchParams =
'props[isSummerTimetable]=false&props[printTimetablesAsA4]=true&props[printTimetablesAsGreyscale]=false&props[template]=default&props[showAddressInfo]=false&props[showPrintButton]=true&props[redirect]=false&template=default';
Expand All @@ -49,16 +41,21 @@ export default {
},
},
tampere: {
timetableUrlResolver: function timetableUrlResolver(
routeTimetableUrlResolver: function routeTimetableUrlResolver(
baseURL,
route,
subscriptionParam,
subscriptionToken,
date,
lang,
) {
const routeNumber = route.shortName.replace(/\D/g, '');
return new URL(`${baseURL}${routeNumber}.html`);
},
stopPdfUrlResolver: function stopPdfUrlResolver(baseURL, stop, date, lang) {
stopTimetableUrlResolver: function stopTimetableUrlResolver(
baseURL,
stop,
date,
lang,
) {
const stopIdSplitted = stop.gtfsId.split(':');
return new URL(`${baseURL}${parseInt(stopIdSplitted[1], 10)}.pdf`);
},
Expand Down
56 changes: 0 additions & 56 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,61 +152,6 @@ function setUpRoutes() {
app.enable('trust proxy');
}

function setUpAvailableRouteTimetables() {
return new Promise(resolve => {
// Stores available route pdf names to config.availableRouteTimetables.HSL
// All routes don't have available pdf and some have their timetable inside other route
// so there is a mapping between route's gtfsId (without HSL: part) and similar gtfsId of
// route that contains timetables
if (config.timetables.HSL) {
// try to fetch available route timetables every four seconds with 4 retries
retryFetch(
`${config.URL.ROUTE_TIMETABLES.HSL}routes.json`,
4,
4000,
{},
config,
)
.then(res => res.json())
.then(
result => {
config.timetables.HSL.setAvailableRouteTimetables(result);
console.log('availableRouteTimetables.HSL loaded');
resolve();
},
err => {
console.log(err);
// If after 5 tries no timetable data is found, start server anyway
resolve();
console.log('availableRouteTimetables.HSL loader failed');
// Continue attempts to fetch available routes in the background for one day once every minute
retryFetch(
`${config.URL.ROUTE_TIMETABLES.HSL}routes.json`,
1440,
60000,
{},
config,
)
.then(res => res.json())
.then(
result => {
config.timetables.HSL.setAvailableRouteTimetables(result);
console.log(
'availableRouteTimetables.HSL loaded after retry',
);
},
error => {
console.log(error);
},
);
},
);
} else {
resolve();
}
});
}

function processTicketTypeResult(result) {
const resultData = result.data;
if (config.availableTickets) {
Expand Down Expand Up @@ -445,7 +390,6 @@ setUpMiddleware();
setUpRoutes();
setUpErrorHandling();
Promise.all([
setUpAvailableRouteTimetables(),
setUpAvailableTickets(),
collectGeoJsonZones(),
fetchCitybikeConfigurations(),
Expand Down
37 changes: 18 additions & 19 deletions test/unit/util/timetableConfigUtils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,42 @@ import { describe, it } from 'mocha';
import * as timetables from '../../../app/configurations/timetableConfigUtils';

describe('timetableConfigUtils', () => {
const baseTimetableURL = 'https://timetabletest.com/timetables/';
describe('timetableUrlResolver', () => {
describe('routeTimetableUrlResolver', () => {
it('should resolve URL correctly for HSL instance', () => {
const baseTimetableURL = 'https://timetabletest.com/?foo=bar';
const timetableHandler = timetables.default.HSL;
timetableHandler.setAvailableRouteTimetables({ 2550: '2550' });
const route = { gtfsId: 'HSL:2550' };
const url = timetableHandler.timetableUrlResolver(
const url = timetableHandler.routeTimetableUrlResolver(
baseTimetableURL,
route,
'20231031',
'en',
);
expect(url.href).to.equal(`${baseTimetableURL}2550.pdf`);
});
it('should resolve URL correctly for HSL instance with authentication param', () => {
const timetableHandler = timetables.default.HSL;
const route = { gtfsId: 'HSL:2550' };
const url = timetableHandler.timetableUrlResolver(
baseTimetableURL,
route,
'foo',
'bar',
expect(url.href).to.equal(
`${baseTimetableURL}&props%5BshowPrintButton%5D=true&props%5Bredirect%5D=false&props%5BlineId%5D=2550&props%5BdateBegin%5D=2023-10-31&props%5BdateEnd%5D=2023-10-31&props%5Blang%5D=en`,
);
expect(url.href).to.equal(`${baseTimetableURL}2550.pdf?foo=bar`);
});
it('should resolve URL for bus lines correctly for tampere instance', () => {
const baseTimetableURL = 'https://timetabletest.com/timetables/';
const timetableHandler = timetables.default.tampere;
const route = { shortName: '11C', mode: 'BUS' };
const url = timetableHandler.timetableUrlResolver(
const url = timetableHandler.routeTimetableUrlResolver(
baseTimetableURL,
route,
'20231031',
'en',
);
expect(url.href).to.equal(`${baseTimetableURL}11.html`);
});
});
describe('stopPdfUrlResolver', () => {
describe('stopTimetableUrlResolver', () => {
it('should resolve correctly for HSL instance', () => {
const hslStopTimetableURL = 'https://timetabletest.com/?foo=bar';
const timetableHandler = timetables.default.HSL;
const stop = { gtfsId: 'HSL:1122127' };
const date = '20231031';
const lang = 'en';
const url = timetableHandler.stopPdfUrlResolver(
const url = timetableHandler.stopTimetableUrlResolver(
hslStopTimetableURL,
stop,
date,
Expand All @@ -54,9 +49,13 @@ describe('timetableConfigUtils', () => {
);
});
it('should resolve correctly for tampere instance', () => {
const baseTimetableURL = 'https://timetabletest.com/timetables/';
const timetableHandler = timetables.default.tampere;
const stop = { gtfsId: 'tampere:0053' };
const url = timetableHandler.stopPdfUrlResolver(baseTimetableURL, stop);
const url = timetableHandler.stopTimetableUrlResolver(
baseTimetableURL,
stop,
);
expect(url.href).to.equal(`${baseTimetableURL}53.pdf`);
});
});
Expand Down