diff --git a/test/check-room-metadata.mjs b/test/check-room-metadata.mjs new file mode 100644 index 0000000..9d2249d --- /dev/null +++ b/test/check-room-metadata.mjs @@ -0,0 +1,98 @@ +import * as assert from 'node:assert'; +import { initTestEnv } from './init-test-env.mjs'; +import { getEnvKey, setEnvKey } from '../tools/lib/envkeys.mjs'; +import { fetchProject } from '../tools/lib/project.mjs'; + +async function fetchTestProject() { + const project = await fetchProject( + await getEnvKey('PROJECT_OWNER'), + await getEnvKey('PROJECT_NUMBER')); + return project; +} + +function assertRoom(project, name, metadata) { + const room = project.rooms.find(r => r.name === name); + assert.ok(room, `Room "${name}" not found in project`); + const roomCopy = Object.assign({}, room); + if (roomCopy.id) { + delete roomCopy.id; + } + const metadataCopy = Object.assign({}, metadata); + metadataCopy.name = metadata.name ?? name; + assert.deepStrictEqual(roomCopy, metadataCopy); +} + +describe('The room definition', function () { + before(function () { + initTestEnv(); + setEnvKey('PROJECT_NUMBER', 'room-metadata'); + setEnvKey('ISSUE_TEMPLATE', 'test/data/template-breakout.yml'); + }); + + it('may be just a name', async function () { + const project = await fetchTestProject(); + const name = 'Just a room'; + assertRoom(project, name, { + label: name, + location: '', + capacity: 30, + vip: false + }); + }); + + it('may inline room information in the name', async function () { + const project = await fetchTestProject(); + const name = 'Inline (75 - basement) (VIP)'; + assertRoom(project, name, { + label: 'Inline', + capacity: 75, + location: 'basement', + vip: true + }); + }); + + it('may contain VIP info in the description', async function () { + const project = await fetchTestProject(); + const name = 'VIP room'; + assertRoom(project, name, { + label: name, + location: '', + capacity: 25, + vip: true + }); + }); + + it('may contain additional metadata in the description', async function () { + const project = await fetchTestProject(); + const name = 'In the back'; + assertRoom(project, name, { + label: name, + location: '2nd floor', + capacity: 40, + vip: false, + type: 'backroom' + }); + }); + + it('may contain invalid metadata in the description', async function () { + const project = await fetchTestProject(); + const name = 'Weird'; + assertRoom(project, name, { + label: name, + location: 'somewhere', + capacity: 30, + vip: false + }); + }); + + it('may define metadata inline and in the description', async function () { + const project = await fetchTestProject(); + const name = 'Hybrid (42)'; + assertRoom(project, name, { + label: 'Hybrid', + location: 'on ze web', + capacity: 42, + vip: false + }); + }); +}); \ No newline at end of file diff --git a/test/data/room-metadata.mjs b/test/data/room-metadata.mjs new file mode 100644 index 0000000..170315f --- /dev/null +++ b/test/data/room-metadata.mjs @@ -0,0 +1,47 @@ +export default { + description: 'meeting: Validation of room metadata, timezone: Etc/UTC', + + days: [ + '2042-04-05' + ], + + slots: [ + '9:00 - 10:00', + '10:00 - 11:00' + ], + + rooms: [ + 'Just a room', + 'Inline (75 - basement) (VIP)', + { + name: 'VIP room', + description: ` +- capacity: 25 +- vip: true +` + }, + { + name: 'In the back', + description: ` +* location: 2nd floor +* capacity: 40 +* vip: false +* type: backroom` + }, + { + name: 'Weird', + description: ` +- +- yes +- location: somewhere` + }, + { + name: 'Hybrid (42)', + description: `capacity: 35 +location: on ze web` + } + ], + + sessions: [ + ] +}; \ No newline at end of file diff --git a/test/stubs.mjs b/test/stubs.mjs index 6ba42f0..f643186 100644 --- a/test/stubs.mjs +++ b/test/stubs.mjs @@ -39,7 +39,21 @@ async function getTestData(testDataId) { } function toGraphQLNameList(arr) { - return arr.map(name => Object.assign({ id: `id_${uid++}`, name })); + return arr.map(item => { + if (typeof item === 'string') { + return { + id: `id_${uid++}`, + name: item + }; + } + else { + return { + id: `id_${uid++}`, + name: item.name, + description: item.description + }; + } + }); } function toGraphQLAuthor(login) { diff --git a/tools/lib/project.mjs b/tools/lib/project.mjs index 2d7c0fa..46c34a5 100644 --- a/tools/lib/project.mjs +++ b/tools/lib/project.mjs @@ -557,7 +557,6 @@ export async function fetchProject(login, id) { ... on ProjectV2SingleSelectFieldOption { id name - description } } } @@ -579,7 +578,6 @@ export async function fetchProject(login, id) { ... on ProjectV2SingleSelectFieldOption { id name - description } } } @@ -762,10 +760,10 @@ export async function fetchProject(login, id) { metadata: parseProjectDescription(project.shortDescription), // List of rooms. For each of them, we return the exact name of the option - // for the "Room" custom field in the project (which includes all info), - // the actual room label, the room's capacity in number of seats, the - // location of the room, and the possible "vip" flag. - // The room's full name should follow the pattern: + // for the "Room" custom field in the project. If the exact name can be + // split into a room label, capacity in number of seats, location, and the + // possible "vip" flag, then that information is used to initialize the + // room's metadata. The room's full name should follow the pattern: // "label (xx - location) (vip)" // Examples: // Catalina (25) @@ -774,18 +772,31 @@ export async function fetchProject(login, id) { // Business (vip) // Small (15) // Plenary (150 - 18th floor) (vip) + // The exact same information can be provided using actual metadata in the + // description of the room, given as a list of key/value pairs such as: + // - capacity: 40 + // - location: 2nd floor + // Possible metadata keys are expected to evolve over time. If the + // information is duplicated in the room name and in metadata, the + // information in the room name will be used roomsFieldId: rooms.id, rooms: rooms.options.map(room => { + const metadata = {}; + (room.description ?? '') + .split(/\n/) + .map(line => line.trim().replace(/^[*\-] /, '').split(/:\s*/)) + .filter(data => data[0] && data[1]) + .filter(data => data[0].toLowerCase() !== 'capacity' || data[1]?.match(/^\d+$/)) + .forEach(data => metadata[data[0].toLowerCase()] = data[1]); const match = room.name.match(/^(.*?)(?:\s*\((\d+)\s*(?:\-\s*([^\)]+))?\))?(?:\s*\((vip)\))?$/i); - return { + return Object.assign(metadata, { id: room.id, name: match[0], label: match[1], - location: match[3] ?? '', - capacity: parseInt(match[2] ?? '30', 10), - vip: !!match[4], - description: room.description - }; + location: match[3] ?? metadata.location ?? '', + capacity: parseInt(match[2] ?? metadata.capacity ?? '30', 10), + vip: !!match[4] || (metadata.vip === 'true') + }); }), // IDs of custom fields used to store validation problems