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

schedules: Use numeric ids for schedules tables primary keys #1086

Merged
merged 4 commits into from
Dec 17, 2024
Merged
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
73 changes: 53 additions & 20 deletions packages/chaire-lib-backend/src/models/db/default.db.queries.ts
Original file line number Diff line number Diff line change
@@ -39,19 +39,34 @@ export const stringifyDataSourceIds = function (dataSourceIds: string[]): string
*
* @param knex The database configuration object
* @param tableName The name of the table on which to execute the operation
* @param id The ID of the object
* @param {string|number} id The ID of the object, numeric for tables that have
* numeric primary keys, or uuid strings for tables that have uuid primary keys
* @param {Object} options Additional options parameter.
* @param {Knex.Transaction} [options.transaction] - transaction that this query
* is part of
* @returns Whether an object with the given ID exists in the table
*/
export const exists = async (knex: Knex, tableName: string, id: string): Promise<boolean> => {
if (!uuidValidate(id)) {
export const exists = async (
knex: Knex,
tableName: string,
id: string | number,
options: {
transaction?: Knex.Transaction;
} = {}
): Promise<boolean> => {
if (typeof id === 'string' && !uuidValidate(id)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment saying that we now accept both uuids (string) or id (integer)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc was updated, also with the new options parameters

throw new TrError(
`Cannot verify if the object exists in ${tableName} because the required parameter id is missing, blank or not a valid uuid`,
'DBQEX0001',
'DatabaseCannotVerifyIfObjectExistsBecauseIdIsMissingOrInvalid'
);
}
try {
const rows = await knex(tableName).count('*').where('id', id);
const query = knex(tableName).count('*').where('id', id);
if (options.transaction) {
query.transacting(options.transaction);
}
const rows = await query;

const count = rows.length > 0 ? rows[0].count : 0;
if (count) {
@@ -177,28 +192,40 @@ export const createMultiple = async <T extends GenericAttributes, U>(
* @param tableName The name of the table on which to execute the operation
* @param parser A parser function which converts the database fields to an
* object's attributes
* @param query The raw select fields to query. Defaults to `*` to read all
* @param select The raw select fields to query. Defaults to `*` to read all
* fields in the table
* @param id The ID of the object to read
* @param {string|number} id The ID of the object to fetch, numeric for tables
* that have numeric primary keys, or uuid strings for tables that have uuid
* primary keys
* @param {Object} options Additional options parameter.
* @param {Knex.Transaction} [options.transaction] - transaction that this query
* is part of
* @returns The object attributes obtained after the `parser` function was run
* on the read record.
*/
export const read = async <T extends GenericAttributes, U>(
knex: Knex,
tableName: string,
parser: ((arg: U) => Partial<T>) | undefined,
query = '*',
id: string
select = '*',
id: string | number,
options: {
transaction?: Knex.Transaction;
} = {}
): Promise<Partial<T>> => {
try {
if (!uuidValidate(id)) {
if (typeof id === 'string' && !uuidValidate(id)) {
throw new TrError(
`Cannot read object from table ${tableName} because the required parameter id is missing, blank or not a valid uuid`,
'DBQRD0001',
'DatabaseCannotReadBecauseIdIsMissingOrInvalid'
);
}
const rows = await knex(tableName).select(knex.raw(query)).where('id', id);
const query = knex(tableName).select(knex.raw(select)).where('id', id);
if (options.transaction) {
query.transacting(options.transaction);
}
const rows = await query;

if (rows.length !== 1) {
throw new TrError(
@@ -226,7 +253,9 @@ export const read = async <T extends GenericAttributes, U>(
* @param tableName The name of the table on which to execute the operation
* @param parser A parser function which converts an object's attributes to db
* fields
* @param id The ID of the record to update
* @param {string|number} id The ID of the object to update, numeric for tables
* that have numeric primary keys, or uuid strings for tables that have uuid
* primary keys
* @param attributes A subset of the object's attributes to update
* @param options Additional options parameter. `returning` specifies which
* field's or fields' values to return after update. `transaction` is an
@@ -237,15 +266,15 @@ export const update = async <T extends GenericAttributes, U>(
knex: Knex,
tableName: string,
parser: ((arg: Partial<T>) => U) | undefined,
id: string,
id: string | number,
attributes: Partial<T>,
options: {
returning?: string;
transaction?: Knex.Transaction;
} = {}
): Promise<string> => {
): Promise<string | number> => {
try {
if (!uuidValidate(id)) {
if (typeof id === 'string' && !uuidValidate(id)) {
throw new TrError(
`Cannot update object with id ${id} from table ${tableName} because the required parameter id is missing, blank or not a valid uuid`,
'DBQUP0001',
@@ -337,21 +366,23 @@ export const updateMultiple = async <T extends GenericAttributes, U>(
*
* @param knex The database configuration object
* @param tableName The name of the table on which to execute the operation
* @param id The ID of the record to delete
* @param {string|number} id The ID of the record to delete, numeric for tables
* that have numeric primary keys, or uuid strings for tables that have uuid
* primary keys
* @param options Additional options parameter. `transaction` is an optional
* transaction of which this delete is part of.
* @returns The ID of the deleted object
*/
export const deleteRecord = async (
knex: Knex,
tableName: string,
id: string,
id: string | number,
options: {
transaction?: Knex.Transaction;
} = {}
) => {
try {
if (!uuidValidate(id)) {
if (typeof id === 'string' && !uuidValidate(id)) {
throw new TrError(
`Cannot verify if object exists in table ${tableName} because the required parameter id is missing, blank or not a valid uuid`,
'DBQDL0001',
@@ -378,19 +409,21 @@ export const deleteRecord = async (
*
* @param knex The database configuration object
* @param tableName The name of the table on which to execute the operation
* @param ids An array of record IDs to delete
* @param {string[]|number[]} ids An array of record IDs to delete, numeric for
* tables that have numeric primary keys, or uuid strings for tables that have
* uuid primary keys
* @param options Additional options parameter. `transaction` is an optional
* transaction of which this delete is part of.
* @returns The array of deleted IDs
*/
export const deleteMultiple = async (
knex: Knex,
tableName: string,
ids: string[],
ids: string[] | number[],
options: {
transaction?: Knex.Transaction;
} = {}
): Promise<string[]> => {
): Promise<string[] | number[]> => {
try {
const query = knex(tableName).whereIn('id', ids).del();
if (options.transaction) {
Original file line number Diff line number Diff line change
@@ -76,7 +76,7 @@ const importParser = function (cacheObject: CacheObjectClass) {
const periodShortname = periodCache.getPeriodShortname();
const period: Partial<SchedulePeriod> = {
period_shortname: periodShortname,
schedule_id: schedule.id,
schedule_id: schedule.integer_id,
outbound_path_id: periodCache.getOutboundPathUuid(),
inbound_path_id: _emptyStringToNull(periodCache.getInboundPathUuid()),
custom_start_at_str:
Original file line number Diff line number Diff line change
@@ -264,7 +264,7 @@ describe(`${objectName}`, function() {
}],
"periods_group_shortname": "all_day",
} as any;
await schedulesDbQueries.create(scheduleForServiceId);
await schedulesDbQueries.save(scheduleForServiceId);

const geojsonCollection = await dbQueries.geojsonCollection({ scenarioId })
expect(geojsonCollection.features.length).toBe(1);

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -221,8 +221,10 @@ describe(`${objectName}`, () => {
const _updatedAttributes = Object.assign({}, newObjectAttributesWithSchedule);
const updatedObject = new ObjectClass(_updatedAttributes, false);
const id = await dbQueries.update(newObjectAttributesWithSchedule.id, updatedObject.getAttributes());
await schedulesDbQueries.create(scheduleForServiceId);
await schedulesDbQueries.create(scheduleForServiceId2);
const schedId1 = await schedulesDbQueries.save(scheduleForServiceId);
(scheduleForServiceId as any).integer_id = schedId1;
const schedId2 = await schedulesDbQueries.save(scheduleForServiceId2);
(scheduleForServiceId2 as any).integer_id = schedId2;
await pathsDbQueries.create(pathAttributes);
expect(id).toBe(newObjectAttributesWithSchedule.id);

Original file line number Diff line number Diff line change
@@ -163,7 +163,7 @@ describe(`${objectName}`, function() {
expect(id).toBe(newObjectAttributes2.id);

// Also create a schedule
await schedulesDbQueries.create({
await schedulesDbQueries.save({
id: uuidV4(),
line_id: lineId,
service_id: newObjectAttributes2.id,
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2024, Polytechnique Montreal and contributors
*
* This file is licensed under the MIT License.
* License text available at https://opensource.org/licenses/MIT
*/
import { Knex } from 'knex';

const scheduleTblName = 'tr_transit_schedules';
const schedulePeriodTblName = 'tr_transit_schedule_periods';
const scheduleTripTblName = 'tr_transit_schedule_trips';

/**
* This migration changes the primary key column of the 3 tables describing
* schedules to be a numeric auto-increment ID. It is not necessary to have a
* uuid and numeric auto-increment IDs will allow more performant duplication
* operations, among other things.
*
* We keep the uuid columns, but it will not be used as primary or foreign keys
* anymore, just for json2capnp conversion.
*
* After this migration, the currently named *_id columns in the 3 schedule
* tables will now be integers instead of uuid, the trip does not force a link
* to the schedule anymore as we have this link through the period.
*
* @param knex The database configuration object
* @returns
*/
export async function up(knex: Knex): Promise<unknown> {
// Rename the current uuid columns, and drop the foreign key constraints for now
await knex.schema.alterTable(scheduleTblName, (table: Knex.TableBuilder) => {
table.renameColumn('id', 'uuid');
});
await knex.schema.alterTable(schedulePeriodTblName, (table: Knex.TableBuilder) => {
table.renameColumn('id', 'uuid');
table.renameColumn('schedule_id', 'schedule_uuid');
table.dropForeign('schedule_id');
});
await knex.schema.alterTable(scheduleTripTblName, (table: Knex.TableBuilder) => {
table.renameColumn('id', 'uuid');
table.renameColumn('schedule_id', 'schedule_uuid');
table.renameColumn('schedule_period_id', 'schedule_period_uuid');
table.dropForeign('schedule_id');
table.dropForeign('schedule_period_id');
});

// Drop all primary keys from table, to better recreate them
await knex.schema.alterTable(scheduleTblName, (table: Knex.TableBuilder) => {
table.dropPrimary();
});
await knex.schema.alterTable(schedulePeriodTblName, (table: Knex.TableBuilder) => {
table.dropPrimary();
});
await knex.schema.alterTable(scheduleTripTblName, (table: Knex.TableBuilder) => {
table.dropPrimary();
});

// Add the new auto-incrementing primary key id columns and columns for
// foreign key with nullable values
await knex.schema.alterTable(scheduleTblName, (table: Knex.TableBuilder) => {
table.increments();
});
await knex.schema.alterTable(schedulePeriodTblName, (table: Knex.TableBuilder) => {
table.increments();
table.integer('schedule_id').nullable();
});
await knex.schema.alterTable(scheduleTripTblName, (table: Knex.TableBuilder) => {
table.increments();
table.integer('schedule_period_id').nullable();
});

// Set the values for the foreign key columns
await knex.raw(`
UPDATE ${schedulePeriodTblName} sp
SET schedule_id = s.id
FROM ${scheduleTblName} s
WHERE sp.schedule_uuid = s.uuid;
`);
await knex.raw(`
UPDATE ${scheduleTripTblName} st
SET schedule_period_id = sp.id
FROM ${schedulePeriodTblName} sp
WHERE st.schedule_period_uuid = sp.uuid;
`);

// Drop the previous foreign uuid key columns, make the new foreign key
// columns not nullable and add foreign key constraints
await knex.schema.alterTable(scheduleTripTblName, (table: Knex.TableBuilder) => {
table.dropColumns('schedule_uuid', 'schedule_period_uuid');
table.integer('schedule_period_id').notNullable().index().alter();
table.foreign('schedule_period_id').references(`${schedulePeriodTblName}.id`).onDelete('CASCADE');
});
return await knex.schema.alterTable(schedulePeriodTblName, (table: Knex.TableBuilder) => {
table.dropColumns('schedule_uuid');
table.integer('schedule_id').notNullable().index().alter();
table.foreign('schedule_id').references(`${scheduleTblName}.id`).onDelete('CASCADE');
});
}

export async function down(knex: Knex): Promise<unknown> {
// Create the uuid foreign key columns in tables
await knex.schema.alterTable(schedulePeriodTblName, (table: Knex.TableBuilder) => {
table.uuid('schedule_uuid').nullable();
});
await knex.schema.alterTable(scheduleTripTblName, (table: Knex.TableBuilder) => {
table.uuid('schedule_uuid').nullable();
table.uuid('schedule_period_uuid').nullable();
});

// Set the values of the uuid columns from current id columns
await knex.raw(`
UPDATE ${schedulePeriodTblName} sp
SET schedule_uuid = s.uuid
FROM ${scheduleTblName} s
WHERE sp.schedule_id = s.id;
`);
await knex.raw(`
UPDATE ${scheduleTripTblName} st
SET schedule_period_uuid = sp.uuid, schedule_uuid = s.uuid
FROM ${schedulePeriodTblName} sp
INNER JOIN ${scheduleTblName} s ON sp.schedule_id = s.id
WHERE st.schedule_period_id = sp.id;
`);

// Delete the id columns, set uuids as primary keys and make foreign key columns not nullable and indexed
await knex.schema.alterTable(scheduleTripTblName, (table: Knex.TableBuilder) => {
table.dropColumns('id', 'schedule_period_id');
table.uuid('uuid').primary().alter();
table.uuid('schedule_period_uuid').notNullable().index().alter();
table.uuid('schedule_uuid').notNullable().index().alter();
});
await knex.schema.alterTable(schedulePeriodTblName, (table: Knex.TableBuilder) => {
table.dropColumns('id', 'schedule_id');
table.uuid('uuid').primary().alter();
table.uuid('schedule_uuid').notNullable().index().alter();
});
await knex.schema.alterTable(scheduleTblName, (table: Knex.TableBuilder) => {
table.dropColumn('id');
table.uuid('uuid').primary().alter();
});

// Revert name change and add foreign key constraints
await knex.schema.alterTable(scheduleTblName, (table: Knex.TableBuilder) => {
table.renameColumn('uuid', 'id');
});
await knex.schema.alterTable(schedulePeriodTblName, (table: Knex.TableBuilder) => {
table.renameColumn('uuid', 'id');
table.renameColumn('schedule_uuid', 'schedule_id');
table.foreign('schedule_id').references('tr_transit_schedules.id').onDelete('CASCADE');
});
return await knex.schema.alterTable(scheduleTripTblName, (table: Knex.TableBuilder) => {
table.renameColumn('uuid', 'id');
table.renameColumn('schedule_uuid', 'schedule_id');
table.renameColumn('schedule_period_uuid', 'schedule_period_id');
table.foreign('schedule_id').references('tr_transit_schedules.id').onDelete('CASCADE');
table.foreign('schedule_period_id').references('tr_transit_schedule_periods.id').onDelete('CASCADE');
});
}
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ import { PathAttributes } from 'transition-common/lib/services/path/Path';
const tableName = 'tr_transit_paths';
const linesTableName = 'tr_transit_lines';
const schedulesTableName = 'tr_transit_schedules';
const periodsTableName = 'tr_transit_schedule_periods';
const tripsTableName = 'tr_transit_schedule_trips';
const scenariosServicesTableName = 'tr_transit_scenario_services';
const st = knexPostgis(knex);
@@ -162,7 +163,8 @@ const geojsonCollection = async (
if (params.scenarioId) {
baseQuery
.innerJoin(`${tripsTableName} as trips`, 'trips.path_id', 'p.id')
.innerJoin(`${schedulesTableName} as sched`, 'sched.id', 'trips.schedule_id')
.innerJoin(`${periodsTableName} as periods`, 'periods.id', 'trips.schedule_period_id')
.innerJoin(`${schedulesTableName} as sched`, 'sched.id', 'periods.schedule_id')
.innerJoin(`${scenariosServicesTableName} as sc`, 'sched.service_id', 'sc.service_id')
.andWhere('sc.scenario_id', params.scenarioId)
.groupBy('p.id', 'l.color', 'l.mode');
@@ -179,7 +181,8 @@ const geojsonCollectionForServices = async (
const baseQuery = getGeojsonBaseQuery(true);
baseQuery
.innerJoin(`${tripsTableName} as trips`, 'trips.path_id', 'p.id')
.innerJoin(`${schedulesTableName} as sched`, 'sched.id', 'trips.schedule_id')
.innerJoin(`${periodsTableName} as periods`, 'periods.id', 'trips.schedule_period_id')
.innerJoin(`${schedulesTableName} as sched`, 'sched.id', 'periods.schedule_id')
.whereIn('sched.service_id', serviceIds)
.groupBy('p.id', 'l.color', 'l.mode');
return await geojsonCollectionFromQuery(baseQuery);
187 changes: 127 additions & 60 deletions packages/transition-backend/src/models/db/transitSchedules.db.queries.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -104,7 +104,7 @@ const prepareServicesForLines = async (
schedule.attributes.periods.push(
Object.assign({}, periodAttributes, {
id: uuidV4(),
schedule_id: schedule.getId(),
schedule_id: schedule.attributes.integer_id || -1,
outbound_path_id: outboundPathId,
inbound_path_id: inboundPathId,
number_of_units: nbVehicles,
Original file line number Diff line number Diff line change
@@ -69,19 +69,22 @@ const pathDistances = path.getCoordinatesDistanceTraveledMeters().map(dist => Ma
const lineId = uuidV4();
const serviceId = uuidV4();
const serviceId2 = uuidV4();
const scheduleIntegerId = 4;

// Schedule with many periods/trips, but one path
const scheduleAttributes1: ScheduleAttributes = {
allow_seconds_based_schedules: false,
id: uuidV4(),
integer_id: scheduleIntegerId,
line_id: lineId,
service_id: serviceId,
is_frozen: false,
data: {},
periods: [{
// Period with start and end hours and multiple trips
schedule_id: uuidV4(),
schedule_id: scheduleIntegerId,
id: uuidV4(),
integer_id: 1,
data: {},
end_at_hour: 12,
inbound_path_id: undefined,
@@ -102,8 +105,7 @@ const scheduleAttributes1: ScheduleAttributes = {
path_id: pathAttributes.id,
seated_capacity: 20,
total_capacity: 50,
schedule_id: uuidV4(),
schedule_period_id: uuidV4(),
schedule_period_id: 1,
data: {}
}, {
arrival_time_seconds: 32416,
@@ -116,8 +118,7 @@ const scheduleAttributes1: ScheduleAttributes = {
path_id: pathAttributes.id,
seated_capacity: 20,
total_capacity: 50,
schedule_id: uuidV4(),
schedule_period_id: uuidV4(),
schedule_period_id: 1,
data: {}
}, {
arrival_time_seconds: 34216,
@@ -130,13 +131,13 @@ const scheduleAttributes1: ScheduleAttributes = {
path_id: pathAttributes.id,
seated_capacity: 20,
total_capacity: 50,
schedule_id: uuidV4(),
schedule_period_id: uuidV4(),
schedule_period_id: 1,
data: {}
}]
}, {
schedule_id: uuidV4(),
schedule_id: scheduleIntegerId,
id: uuidV4(),
integer_id: 4,
data: {},
custom_start_at_str: "13:15",
custom_end_at_str: "17:24",
@@ -156,13 +157,12 @@ const scheduleAttributes1: ScheduleAttributes = {
path_id: pathAttributes.id,
seated_capacity: 20,
total_capacity: 50,
schedule_id: uuidV4(),
schedule_period_id: uuidV4(),
schedule_period_id: 4,
data: {}
}]
}, {
// Period with custom start and end, without trips
schedule_id: uuidV4(),
schedule_id: scheduleIntegerId,
id: uuidV4(),
data: {},
custom_start_at_str: "18:00",
@@ -178,16 +178,19 @@ const scheduleAttributes1: ScheduleAttributes = {
};

// Simple second schedule, one trip in each direction, the actual coordinates and routability are not important
const scheduleIntegerId2 = 5;
const scheduleAttributes2: ScheduleAttributes = {
allow_seconds_based_schedules: false,
id: uuidV4(),
integer_id: scheduleIntegerId2,
line_id: lineId,
service_id: serviceId2,
is_frozen: false,
data: {},
periods: [{
schedule_id: uuidV4(),
schedule_id: scheduleIntegerId2,
id: uuidV4(),
integer_id: 2,
data: {},
end_at_hour: 12,
inbound_path_id: pathAttributes2.id,
@@ -207,8 +210,7 @@ const scheduleAttributes2: ScheduleAttributes = {
path_id: pathAttributes.id,
seated_capacity: 20,
total_capacity: 50,
schedule_id: uuidV4(),
schedule_period_id: uuidV4(),
schedule_period_id: 2,
data: {}
},
{
@@ -223,8 +225,7 @@ const scheduleAttributes2: ScheduleAttributes = {
path_id: pathAttributes2.id,
seated_capacity: 20,
total_capacity: 50,
schedule_id: uuidV4(),
schedule_period_id: uuidV4(),
schedule_period_id: 2,
data: {}
}]
}],
Original file line number Diff line number Diff line change
@@ -135,7 +135,7 @@ const generateAndImportSchedules = async (
await linesDbQueries.update(line.getId(), line.attributes, { returning: 'id' });
// Save schedules for line
const saveSchedPromises = newSchedules.map((schedule) => {
scheduleDbQueries.create(schedule.attributes);
scheduleDbQueries.save(schedule.attributes);
});
await Promise.all(saveSchedPromises);
await lineObjectToCache(line);
@@ -229,7 +229,7 @@ const createSchedule = (
const period = periods[j];
const periodSchedule = {
id: uuidV4(),
schedule_id: schedule.getId(),
schedule_id: schedule.attributes.integer_id,
start_at_hour: period.startAtHour,
end_at_hour: period.endAtHour,
period_shortname: period.shortname,
@@ -332,8 +332,7 @@ const generateExactTrips = (schedule: Schedule, periods: TripAndStopTimes[][], i

const newTrip = {
id: uuidV4(),
schedule_id: schedule.getId(),
schedule_period_id: schedule.getAttributes().periods[periodIndex].id,
schedule_period_id: schedule.getAttributes().periods[periodIndex].integer_id,
path_id: pathId,
departure_time_seconds: tripDepartureTimeSeconds,
arrival_time_seconds: tripArrivalTimeSeconds,
@@ -478,9 +477,14 @@ const generateSchedulesForLine = async (
}

line.addSchedule(schedule);
if (existingSchedules[schedule.attributes.service_id]) {
if (
existingSchedules[schedule.attributes.service_id] &&
typeof existingSchedules[schedule.attributes.service_id].attributes.integer_id === 'number'
) {
// Delete previous schedules for this service
await scheduleDbQueries.delete(existingSchedules[schedule.attributes.service_id].getId());
await scheduleDbQueries.delete(
existingSchedules[schedule.attributes.service_id].attributes.integer_id as number
);
}
}

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions packages/transition-common/src/services/schedules/Schedule.ts
Original file line number Diff line number Diff line change
@@ -19,8 +19,7 @@ import SaveUtils from 'chaire-lib-common/lib/services/objects/SaveUtils';
import CollectionManager from 'chaire-lib-common/lib/utils/objects/CollectionManager';

export interface SchedulePeriodTrip extends GenericAttributes {
schedule_id: string;
schedule_period_id: string;
schedule_period_id?: number;
path_id: string;
unit_id?: string;
block_id?: string;
@@ -35,7 +34,7 @@ export interface SchedulePeriodTrip extends GenericAttributes {
}

export interface SchedulePeriod extends GenericAttributes {
schedule_id: string;
schedule_id?: number;
outbound_path_id?: string;
inbound_path_id?: string;
period_shortname?: string;
@@ -109,11 +108,13 @@ class Schedule extends ObjectWithHistory<ScheduleAttributes> implements Saveable
getClonedAttributes(deleteSpecifics = true): Partial<ScheduleAttributes> {
const clonedAttributes = super.getClonedAttributes(deleteSpecifics);
if (deleteSpecifics) {
delete clonedAttributes.integer_id;
const periods = clonedAttributes.periods;
if (periods) {
for (let i = 0; i < periods.length; i++) {
const period = periods[i] as Partial<SchedulePeriod>;
delete period.id;
delete period.integer_id;
delete period.schedule_id;
delete period.created_at;
delete period.updated_at;
@@ -122,7 +123,7 @@ class Schedule extends ObjectWithHistory<ScheduleAttributes> implements Saveable
for (let j = 0; j < trips.length; j++) {
const trip = trips[j] as Partial<SchedulePeriodTrip>;
delete trip.id;
delete trip.schedule_id;
delete trip.integer_id;
delete trip.schedule_period_id;
delete trip.created_at;
delete trip.updated_at;
Original file line number Diff line number Diff line change
@@ -16,13 +16,16 @@ export interface DuplicateScheduleOptions {

/**
* duplicate a schedule object, with all trips and periods
*
* FIXME Will be moved to backend for easier copy of schedules
*/
export const duplicateSchedule = async (
baseSchedule: Schedule,
{ lineId = false, serviceId = false, pathIdsMapping = {} }: DuplicateScheduleOptions
): Promise<Schedule> => {
const newScheduleAttribs = baseSchedule.getClonedAttributes(true);
newScheduleAttribs.id = uuidV4();
delete newScheduleAttribs.integer_id;

if (serviceId) newScheduleAttribs.service_id = serviceId;
if (lineId) newScheduleAttribs.line_id = lineId;
@@ -31,7 +34,7 @@ export const duplicateSchedule = async (
for (let periodI = 0, countPeriods = newScheduleAttribs.periods.length; periodI < countPeriods; periodI++) {
const period = newScheduleAttribs.periods[periodI];
period.id = uuidV4();
period.schedule_id = newScheduleAttribs.id;
delete period.schedule_id;

if (period.inbound_path_id && pathIdsMapping[period.inbound_path_id]) {
period.inbound_path_id = pathIdsMapping[period.inbound_path_id];
@@ -43,8 +46,7 @@ export const duplicateSchedule = async (
for (let tripI = 0, countTrips = period.trips.length; tripI < countTrips; tripI++) {
const trip = period.trips[tripI];
trip.id = uuidV4();
trip.schedule_period_id = period.id;
trip.schedule_id = newScheduleAttribs.id;
delete trip.schedule_period_id;
if (trip.path_id && pathIdsMapping[trip.path_id]) {
trip.path_id = pathIdsMapping[trip.path_id];
}
Original file line number Diff line number Diff line change
@@ -243,7 +243,7 @@ describe('generateForPeriod', () => {
const smallPeriodsForUpdate: SchedulePeriod[] = [{
// Period with start and end hours and multiple trips
id: uuidV4(),
schedule_id: scheduleAttributesForUpdate.id,
schedule_id: scheduleAttributesForUpdate.integer_id,
inbound_path_id: undefined,
outbound_path_id: path.getId(),
period_shortname: "period1",
@@ -253,7 +253,7 @@ describe('generateForPeriod', () => {
trips: []
}, {
id: uuidV4(),
schedule_id: scheduleAttributesForUpdate.id,
schedule_id: scheduleAttributesForUpdate.integer_id,
inbound_path_id: undefined,
outbound_path_id: path.getId(),
period_shortname: "period2",
Original file line number Diff line number Diff line change
@@ -12,7 +12,8 @@ const defaultLineId = uuidV4();
const defaultServiceId = uuidV4();
const defaultPathId = uuidV4();
const defaultScheduleId = uuidV4();
const periodIds = [uuidV4(), uuidV4(), uuidV4()];
const periodIds = [1, 2, 3];
const scheduleIntegerId = 1;

// FIXME: departure/arrival times need to be set to number[] even if they have null. Make sure ScheduleAttributes have the proper types or review how times are used.
export const getScheduleAttributes: (params: any) => ScheduleAttributes = ({
@@ -23,6 +24,7 @@ export const getScheduleAttributes: (params: any) => ScheduleAttributes = ({
}) => {
return {
id: scheduleId,
integer_id: scheduleIntegerId,
allow_seconds_based_schedules: false,
line_id: lineId,
service_id: serviceId,
@@ -31,8 +33,9 @@ export const getScheduleAttributes: (params: any) => ScheduleAttributes = ({
data: {},
periods: [{
// Period with start and end hours and multiple trips
id: periodIds[0],
schedule_id: scheduleId,
id: uuidV4(),
integer_id: periodIds[0],
schedule_id: scheduleIntegerId,
inbound_path_id: undefined,
outbound_path_id: pathId,
interval_seconds: 1800,
@@ -42,7 +45,7 @@ export const getScheduleAttributes: (params: any) => ScheduleAttributes = ({
data: {},
trips: [{
id: uuidV4(),
schedule_id: scheduleId,
integer_id: 1,
schedule_period_id: periodIds[0],
data: {},
path_id: pathId,
@@ -56,7 +59,7 @@ export const getScheduleAttributes: (params: any) => ScheduleAttributes = ({
total_capacity: 50
}, {
id: uuidV4(),
schedule_id: scheduleId,
integer_id: 2,
schedule_period_id: periodIds[0],
data: {},
path_id: pathId,
@@ -70,7 +73,7 @@ export const getScheduleAttributes: (params: any) => ScheduleAttributes = ({
total_capacity: 50
}, {
id: uuidV4(),
schedule_id: scheduleId,
integer_id: 3,
schedule_period_id: periodIds[0],
data: {},
path_id: pathId,
@@ -84,8 +87,9 @@ export const getScheduleAttributes: (params: any) => ScheduleAttributes = ({
total_capacity: 50
}]
}, {
id: periodIds[1],
schedule_id: scheduleId,
id: uuidV4(),
integer_id: periodIds[1],
schedule_id: scheduleIntegerId,
// Period with custom start and end, with a single trip
custom_start_at_str: "13:15",
custom_end_at_str: "17:24",
@@ -98,7 +102,7 @@ export const getScheduleAttributes: (params: any) => ScheduleAttributes = ({
data: {},
trips: [{
id: uuidV4(),
schedule_id: scheduleId,
integer_id: 4,
schedule_period_id: periodIds[1],
data: {},
path_id: pathId,
@@ -113,8 +117,9 @@ export const getScheduleAttributes: (params: any) => ScheduleAttributes = ({
}]
}, {
// Period with custom start and end, without trips
id: periodIds[2],
schedule_id: scheduleId,
id: uuidV4(),
integer_id: periodIds[2],
schedule_id: scheduleIntegerId,
data: {},
custom_start_at_str: '18:00',
custom_end_at_str: '23:00',
Original file line number Diff line number Diff line change
@@ -24,26 +24,25 @@ test('Duplicate schedule, same line path and services', async () => {
// Validate the schedule's data
const expectedSched = getScheduleAttributes({ pathId, scheduleId: newSchedule.getId() });
const actualSched = newSchedule.getAttributes();
const expectedBaseSchedules = _omit(expectedSched, 'periods');
const expectedBaseSchedules = _omit(expectedSched, 'periods', 'integer_id');
const actualSchedule = _omit(actualSched, 'periods');
const expectedPeriods = expectedSched.periods;
const actualPeriods = actualSched.periods;
expect(actualSchedule).toEqual(expectedBaseSchedules);

// Validate the period's data and id propagation
for (let i = 0; i < expectedPeriods.length; i++) {
const expectedPeriod = _omit(expectedPeriods[i], ['id', 'trips']);
const expectedPeriod = _omit(expectedPeriods[i], ['id', 'trips', 'integer_id', 'schedule_id']);
const actualPeriod = _omit(actualPeriods[i], ['id', 'trips']);
const expectedTrips = expectedPeriods[i].trips;
const actualTrips = actualPeriods[i].trips;
const periodId = actualPeriods[i].id;
const periodId = actualPeriods[i].integer_id;
expect(actualPeriod).toEqual(expectedPeriod);

// Validate the trip's data and id propagation
for (let j = 0; j < expectedTrips.length; j++) {
const expectedTrip = _omit(expectedTrips[j], 'id');
const expectedTrip = _omit(expectedTrips[j], 'id', 'integer_id', 'schedule_period_id');
const actualTrip = _omit(actualTrips[j], 'id');
expectedTrip.schedule_period_id = periodId;
expect(actualTrip).toEqual(expectedTrip);
}
}
@@ -65,27 +64,25 @@ test('Duplicate schedule, different line, path and services', async () => {
// Validate the schedule's data
const expectedSched = getScheduleAttributes({ pathId: newPathId, lineId: newLineId, serviceId: newServiceId, scheduleId: newSchedule.getId() });
const actualSched = newSchedule.getAttributes();
const expectedBaseSchedules = _omit(expectedSched, 'periods');
const expectedBaseSchedules = _omit(expectedSched, 'periods', 'integer_id');
const actualSchedule = _omit(actualSched, 'periods');
const expectedPeriods = expectedSched.periods;
const actualPeriods = actualSched.periods;
expect(actualSchedule).toEqual(expectedBaseSchedules);

// Validate the period's data and id propagation
for (let i = 0; i < expectedPeriods.length; i++) {
const expectedPeriod = _omit(expectedPeriods[i], ['id', 'trips']);
const expectedPeriod = _omit(expectedPeriods[i], ['id', 'trips', 'integer_id', 'schedule_id']);
const actualPeriod = _omit(actualPeriods[i], ['id', 'trips']);
expect(actualPeriod.outbound_path_id).not.toEqual(schedule.getAttributes().periods[i].outbound_path_id);
const expectedTrips = expectedPeriods[i].trips;
const actualTrips = actualPeriods[i].trips;
const periodId = actualPeriods[i].id;
expect(actualPeriod).toEqual(expectedPeriod);

// Validate the trip's data and id propagation
for (let j = 0; j < expectedTrips.length; j++) {
const expectedTrip = _omit(expectedTrips[j], 'id');
const expectedTrip = _omit(expectedTrips[j], 'id', 'integer_id', 'schedule_period_id');
const actualTrip = _omit(actualTrips[j], 'id');
expectedTrip.schedule_period_id = periodId;
expect(actualTrip).toEqual(expectedTrip);
}
}

Unchanged files with check annotations Beta

interface PreferencesModelWithIdAndData extends PreferencesModel {
id: string;
data: { [key: string]: any };

Check warning on line 24 in packages/chaire-lib-common/src/config/Preferences.ts

GitHub Actions / code-lint

Unexpected any. Specify a different type
}
const prefChangeEvent = 'change';
}
// Not implemented for Preferences, we should never delete the preferences object.
public delete(_socket: any): Promise<any> {

Check warning on line 247 in packages/chaire-lib-common/src/config/Preferences.ts

GitHub Actions / code-lint

Unexpected any. Specify a different type

Check warning on line 247 in packages/chaire-lib-common/src/config/Preferences.ts

GitHub Actions / code-lint

Unexpected any. Specify a different type
return new Promise((resolve) => {
resolve(null);
});
}
// TODO: type this:
public get(path: string, defaultValue: unknown = undefined): any {

Check warning on line 324 in packages/chaire-lib-common/src/config/Preferences.ts

GitHub Actions / code-lint

Unexpected any. Specify a different type
return super.get(path, defaultValue);
}
/** Hexadecimal strings of the various colors that should be available */
colors: string[];
};
[key: string]: any;

Check warning on line 32 in packages/chaire-lib-common/src/config/defaultPreferences.config.ts

GitHub Actions / code-lint

Unexpected any. Specify a different type
}
// TODO: Type more fields
} & AdditionalConfig;
// Initialize default configuration
const projectConfig: ProjectConfiguration<any> = {

Check warning on line 103 in packages/chaire-lib-common/src/config/shared/project.config.ts

GitHub Actions / code-lint

Unexpected any. Specify a different type
mapDefaultCenter: { lon: -73.6131, lat: 45.5041 },
separateAdminLoginPage: false,
projectShortname: 'default',
class DataSource extends ObjectWithHistory<DataSourceAttributes> {
protected static displayName = 'DataSource';
private _collectionManager: any;

Check warning on line 37 in packages/chaire-lib-common/src/services/dataSource/DataSource.ts

GitHub Actions / code-lint

Unexpected any. Specify a different type
constructor(attributes = {}, isNew: boolean, collectionManager?) {
super(attributes, isNew);
return this.attributes.name || this.attributes.shortname || this.getId();
}
get collectionManager(): any {

Check warning on line 49 in packages/chaire-lib-common/src/services/dataSource/DataSource.ts

GitHub Actions / code-lint

Unexpected any. Specify a different type
// TODO: test or use dependency injection
return this._collectionManager;
}
[Property in keyof TEvent[EventArgsKey]]: TEvent[EventArgsKey][Property];
}) => void
): void;
emit(event: string | Event, ...args: any[]): void;

Check warning on line 50 in packages/chaire-lib-common/src/services/events/EventManager.ts

GitHub Actions / code-lint

Unexpected any. Specify a different type
emitProgress(progressName: string, completeRatio: number): void;
once(event: string | Event, callback: (data: any) => void): void;

Check warning on line 52 in packages/chaire-lib-common/src/services/events/EventManager.ts

GitHub Actions / code-lint

Unexpected any. Specify a different type
on(event: string | Event, callback: (...data: any) => void): void;
addListener(event: string | Event, callback: (...data: any) => void): void;
removeListener(event: string | Event, callback: (...data: any) => void): void;