Skip to content
Open
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
7 changes: 5 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
UserSettings,
} from './services/dynamo';
import { setUserStatus, setUserPresence, getUserByEmail, postMessage, SlackUser } from './services/slack';
import { getUrlForRoom } from './services/rooms';
import { Handler } from 'aws-lambda';
import { InvocationRequest } from 'aws-sdk/clients/lambda';
import { getStatusForUserEvent } from './utils/map-event-status';
Expand Down Expand Up @@ -49,9 +50,11 @@ const sendUpcomingEventMessage = async (
event: CalendarEvent | null,
settings: UserSettings,
) => {
if (!event || !user) return;
if (!event || !user) {
return;
}
const url = await getEventLocationUrl(event, settings);

const url = getEventLocationUrl(event, settings);
if (!url) return;

return await postMessage(token, { text: `Join *${event.name}* at: ${url}`, channel: user.id });
Expand Down
63 changes: 63 additions & 0 deletions src/services/rooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { roomsJsonUrl } from '../utils/urls';

export type Capabilities = {
capacity: number;
bookable: boolean;
tv: boolean;
pc: boolean;
audio: boolean;
};

export type Room = {
id: string;
url: string;
nicknames: string[];
address: string;
office: string;
floor: number;
type: 'lounge' | 'phone-booth' | 'meeting' | 'mothers';
capabilities: Capabilities;
};

const roomData: Room[] = [];

const hasNickname = (room: Room, query: string) => {
return !!(
(room && room.nicknames.length && room.nicknames.find(n => n.toLowerCase().indexOf(query) > -1)) ||
room.id.replace(/-/gi, ' ').indexOf(query) > -1
);
};

const http = async <T>(url: string): Promise<Room[]> => {
return fetch(url)
.then(response => {
return response.json() as Promise<Room[]>;
})
.catch(e => {
console.log('Failed to fetch rooms data', e);
return [];
});
};

const fetchRoomsData = async (): Promise<Room[]> => {
if (roomData.length === 0) {
const fetchedData = await http<Array<Room>>(roomsJsonUrl());
roomData.push(...fetchedData);
}

return roomData;
};

export const getUrlForRoom = async (query: string): Promise<string[]> => {
const data = await fetchRoomsData();

const foundRooms: string[] = [];
if (data && data.length) {
data.forEach(room => {
if (hasNickname(room, query)) {
foundRooms.push(room.url);
}
});
}
return foundRooms;
};
52 changes: 26 additions & 26 deletions src/utils/__tests__/eventHelper.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const baseEvent: CalendarEvent = {

describe('getEventLocationUrl', () => {
describe('Given a null event', () => {
test('Returns null', () => {
const url = getEventLocationUrl(null, baseUserSettings);
test('Returns null', async () => {
const url = await getEventLocationUrl(null, baseUserSettings);

expect(url).toBeNull();
});
Expand All @@ -24,127 +24,127 @@ describe('getEventLocationUrl', () => {
describe('Url-only locations', () => {
const testUrl = 'https://my.test.url';

test('Url-only returns the url', () => {
test('Url-only returns the url', async () => {
const event: CalendarEvent = {
...baseEvent,
location: `${testUrl}`,
};
const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBe(testUrl);
});
test('Front-padded url returns just the url', () => {
test('Front-padded url returns just the url', async () => {
const event: CalendarEvent = {
...baseEvent,
location: ` ${testUrl}`,
};
const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBe(testUrl);
});
test('Back-padded url returns just the url', () => {
test('Back-padded url returns just the url', async () => {
const event: CalendarEvent = {
...baseEvent,
location: `${testUrl} `,
};
const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBe(testUrl);
});
});

describe('Non-url locations', () => {
test('Gibberish returns null', () => {
test('Gibberish returns null', async () => {
const event: CalendarEvent = {
...baseEvent,
location: 'asdfqweoriu-123-wequio',
};

const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBeNull();
});
test('Names returns null', () => {
test('Names returns null', async () => {
const event: CalendarEvent = {
...baseEvent,
location: 'Jane Smith',
};

const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBeNull();
});
test('Meeting room names return null', () => {
test('Meeting room names return null', async () => {
const event: CalendarEvent = {
...baseEvent,
location: 'Michael Jordan',
};

const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBeNull();
});
test('Semicolon-separated names return null', () => {
test('Semicolon-separated names return null', async () => {
const event: CalendarEvent = {
...baseEvent,
location: 'Michael Jordan; Candace Parker; Bob Smith',
};

const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBeNull();
});
});

describe('Mixed locations', () => {
const testUrl = 'https://my.test.url/stuff?123';
test('Url-first returns the url', () => {
test('Url-first returns the url', async () => {
const event: CalendarEvent = {
...baseEvent,
location: `${testUrl} Michael Jordan`,
};

const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBe(testUrl);
});
test('Semicolon-separated returns the url', () => {
test('Semicolon-separated returns the url', async() => {
const event: CalendarEvent = {
...baseEvent,
location: `${testUrl}; Michael Jordan`,
};

const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBe(testUrl);
});
test('Url last returns the url', () => {
test('Url last returns the url', async () => {
const event: CalendarEvent = {
...baseEvent,
location: `Michael Jordan; ${testUrl}`,
};

const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBe(testUrl);
});
test('Url last with semicolon returns the url', () => {
test('Url last with semicolon returns the url', async () => {
const event: CalendarEvent = {
...baseEvent,
location: `Michael Jordan; ${testUrl};`,
};

const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBe(testUrl);
});
test('Url middle returns the url', () => {
test('Url middle returns the url', async () => {
const event: CalendarEvent = {
...baseEvent,
location: `Michael Jordan; ${testUrl}; Scrum Masters;`,
};

const url = getEventLocationUrl(event, baseUserSettings);
const url = await getEventLocationUrl(event, baseUserSettings);

expect(url).toBe(testUrl);
});
Expand Down
32 changes: 28 additions & 4 deletions src/utils/eventHelper.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { CalendarEvent } from '../services/calendar/calendar';
import { UserSettings } from '../services/dynamo';
import { getUrlForRoom } from '../services/rooms';

export const getEventLocationUrl = (event: CalendarEvent | null, settings: UserSettings) => {
const urlRegex = /((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/g;
const urlRegex = /((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/g;

export const getEventLocationUrl = async (
event: CalendarEvent | null,
settings: UserSettings,
): Promise<string | null> => {
const location = (event && event.location) || '';
const urlMatches = location.match(urlRegex);
if (settings.zoomLinksDisabled || !event || !location) {
return null;
}

if (settings.zoomLinksDisabled || !event || !location || !urlMatches || urlMatches.length === 0) {
const urlMatches = location.match(urlRegex) || (await getRoomLocationUrl(event));

if (!urlMatches || urlMatches.length === 0) {
return null;
}

Expand All @@ -16,3 +25,18 @@ export const getEventLocationUrl = (event: CalendarEvent | null, settings: UserS
const url = urlMatches[0];
return url.endsWith(';') ? url.slice(0, url.length - 1) : url;
};

export const getRoomLocationUrl = async (event: CalendarEvent | null): Promise<string[]> => {
const location = (event && event.location) || '';
const locations = location.split(';');

const roomUrls: string[] = [];
locations.forEach(async l => {
const url = await getUrlForRoom(location);
if (url) {
roomUrls.push(...url);
}
});

return roomUrls;
};
1 change: 1 addition & 0 deletions src/utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const createUserUrl = () => `${process.env.IS_OFFLINE ? config.hosts.dev
export const slackInstallUrl = () => `${process.env.IS_OFFLINE ? config.hosts.dev : config.hosts.prod}/slack/install`;
export const authorizeMicrosoftGraphUrl = () =>
`${process.env.IS_OFFLINE ? config.hosts.dev : config.hosts.prod}/authorize-microsoft-graph`;
export const roomsJsonUrl = () => 'https://rooms.hudltools.com/static/data/rooms.json';