Skip to content

Commit

Permalink
Add ability to read room metadata from the room description
Browse files Browse the repository at this point in the history
Implements part of #134 (comment),
making it possible to define the room's metadata within the description field
next to the definition of the room in the GitHub project.

Note that this does not make any practical change to available semantics. The
code does not yet support any more metadata key (such as "availability"). To
be done later on.
  • Loading branch information
tidoust committed Sep 10, 2024
1 parent 0f77187 commit 3117f77
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 13 deletions.
98 changes: 98 additions & 0 deletions test/check-room-metadata.mjs
Original file line number Diff line number Diff line change
@@ -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
});
});
});
47 changes: 47 additions & 0 deletions test/data/room-metadata.mjs
Original file line number Diff line number Diff line change
@@ -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: [
]
};
16 changes: 15 additions & 1 deletion test/stubs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
35 changes: 23 additions & 12 deletions tools/lib/project.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,6 @@ export async function fetchProject(login, id) {
... on ProjectV2SingleSelectFieldOption {
id
name
description
}
}
}
Expand All @@ -579,7 +578,6 @@ export async function fetchProject(login, id) {
... on ProjectV2SingleSelectFieldOption {
id
name
description
}
}
}
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down

0 comments on commit 3117f77

Please sign in to comment.