-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds support for the notion.so tracker. We can distinguish two types of views onto tickets: 1. The modal overlay when clicking on a ticket on a board 2. The single page view of a ticket Note: Tickets are "pages" in notion.so's domain. Mention support for notion in Readme and popup [closes #163] Co-authored-by: Paul Meinhardt <paul@bitcrowd.net>
- Loading branch information
1 parent
50ceb9b
commit f30a1e2
Showing
5 changed files
with
209 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/** | ||
* notion.so adapter | ||
* | ||
* The adapter extracts the UUID of a selected notion.so task ("page") from the | ||
* page URL and uses the notion.so API to retrieve the corresponding task | ||
* information. | ||
* | ||
* Note: this adapter uses notion.so's internal v3 API as there is no public | ||
* API yet. | ||
*/ | ||
|
||
import { match } from 'micro-match'; | ||
|
||
import client from '../client'; | ||
|
||
/** | ||
* Turns a slugged page ID without dashes into a dasherized RFC 4122 UUID. | ||
* UUID-Format: 96ec637d-e4b0-4a5e-acf3-8d4d9a1b2e4b | ||
*/ | ||
function uuid(slugId) { | ||
return [ | ||
slugId.substring(0, 8), | ||
slugId.substring(8, 12), | ||
slugId.substring(12, 16), | ||
slugId.substring(16, 20), | ||
slugId.substring(20), | ||
].join('-'); | ||
} | ||
|
||
function getPageFromPath(path) { | ||
const { slug } = match('/:organization/:slug', path); | ||
if (!slug) return null; | ||
|
||
return slug.replace(/.*-/, ''); // strip title from slug | ||
} | ||
|
||
function getSelectedPageId(loc) { | ||
const { pathname: path, searchParams: params } = new URL(loc.href); | ||
const isPageModal = params.has('p'); | ||
const slugId = isPageModal ? params.get('p') : getPageFromPath(path); | ||
|
||
if (!slugId) return null; | ||
return uuid(slugId); | ||
} | ||
|
||
function extractTicketInfo(result) { | ||
const { value } = result; | ||
if (!value) return null; | ||
|
||
const { id, type } = value; | ||
if (type !== 'page') return null; | ||
|
||
const title = value.properties.title[0][0]; | ||
return { id, title, type }; | ||
} | ||
|
||
function getTickets(response, id) { | ||
return (response.results || []) | ||
.map(extractTicketInfo) | ||
.filter(Boolean) | ||
.filter((t) => t.id === id); | ||
} | ||
|
||
async function scan(loc) { | ||
if (loc.host !== 'www.notion.so') return []; | ||
|
||
const id = getSelectedPageId(loc); | ||
|
||
if (!id) return []; | ||
|
||
const api = client(`https://${loc.host}`); | ||
const request = { json: { requests: [{ table: 'block', id }] } }; | ||
const response = await api.post('api/v3/getRecordValues', request).json(); | ||
|
||
return getTickets(response, id); | ||
} | ||
|
||
export default scan; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import client from '../client'; | ||
import loc from './__helpers__/location'; | ||
import scan from './notion'; | ||
|
||
jest.mock('../client', () => jest.fn()); | ||
|
||
describe('notion adapter', () => { | ||
const id = '5b1d7dd7-9107-4890-b2ec-83175b8eda83'; | ||
const title = 'Add notion.so support'; | ||
const slugId = '5b1d7dd791074890b2ec83175b8eda83'; | ||
const slug = `Add-notion-so-support-${slugId}`; | ||
const response = { | ||
results: [ | ||
{ | ||
role: 'editor', | ||
value: { | ||
id, | ||
type: 'page', | ||
properties: { title: [[title]] }, | ||
}, | ||
}, | ||
], | ||
}; | ||
|
||
const ticket = { id, title, type: 'page' }; | ||
const api = { post: jest.fn() }; | ||
|
||
beforeEach(() => { | ||
api.post.mockReturnValue({ json: () => response }); | ||
client.mockReturnValue(api); | ||
}); | ||
|
||
afterEach(() => { | ||
api.post.mockReset(); | ||
client.mockReset(); | ||
}); | ||
|
||
it('returns an empty array when not on a www.notion.so page', async () => { | ||
const result = await scan(loc('another-domain.com')); | ||
expect(api.post).not.toHaveBeenCalled(); | ||
expect(result).toEqual([]); | ||
}); | ||
|
||
it('uses the notion.so api', async () => { | ||
const location = loc('www.notion.so', `/notionuser/${slug}`); | ||
await scan(location); | ||
expect(client).toHaveBeenCalledWith('https://www.notion.so'); | ||
expect(api.post).toHaveBeenCalled(); | ||
}); | ||
|
||
it('returns an empty array when the current page is a board view', async () => { | ||
const res = { | ||
results: [ | ||
{ | ||
role: 'editor', | ||
value: { id, type: 'collection_view_page' }, | ||
}, | ||
], | ||
}; | ||
api.post.mockReturnValueOnce({ json: () => res }); | ||
|
||
const location = loc( | ||
'www.notion.so', | ||
`/notionuser/${slugId}`, | ||
'?v=77ff97cab6ff4beab7fa6e27f992dd5e' | ||
); | ||
const result = await scan(location); | ||
const request = { requests: [{ table: 'block', id }] }; | ||
expect(api.post).toHaveBeenCalledWith('api/v3/getRecordValues', { | ||
json: request, | ||
}); | ||
expect(result).toEqual([]); | ||
}); | ||
|
||
it('returns an emtpy array when the page does not exist', async () => { | ||
const res = { results: [{ role: 'editor' }] }; | ||
api.post.mockReturnValueOnce({ json: () => res }); | ||
|
||
const location = loc('www.notion.so', `/notionuser/${slug}`); | ||
const result = await scan(location); | ||
const request = { requests: [{ table: 'block', id }] }; | ||
expect(api.post).toHaveBeenCalledWith('api/v3/getRecordValues', { | ||
json: request, | ||
}); | ||
expect(result).toEqual([]); | ||
}); | ||
|
||
it('returns an empty array if the page id does not match the requested one', async () => { | ||
const otherId = '7c1e7ee7-9107-4890-b2ec-83175b8edv99'; | ||
const otherSlugId = otherId.replace(/-/g, ''); | ||
|
||
const location = loc( | ||
'www.notion.so', | ||
`/notionuser/Some-ticket-${otherSlugId}` | ||
); | ||
const result = await scan(location); | ||
const request = { requests: [{ table: 'block', id: otherId }] }; | ||
expect(api.post).toHaveBeenCalledWith('api/v3/getRecordValues', { | ||
json: request, | ||
}); | ||
expect(result).toEqual([]); | ||
}); | ||
|
||
it('extracts tickets from page modals (board view)', async () => { | ||
const location = loc( | ||
'www.notion.so', | ||
'/notionuser/0e8608aa770a4d36a246d7a3c64f51af', | ||
`?v=77ff97cab6ff4beab7fa6e27f992dd5e&p=${slugId}` | ||
); | ||
const result = await scan(location); | ||
const request = { requests: [{ table: 'block', id }] }; | ||
expect(api.post).toHaveBeenCalledWith('api/v3/getRecordValues', { | ||
json: request, | ||
}); | ||
expect(result).toEqual([ticket]); | ||
}); | ||
|
||
it('extracts tickets from the page view', async () => { | ||
const location = loc('www.notion.so', `/notionuser/${slug}`); | ||
const result = await scan(location); | ||
const request = { requests: [{ table: 'block', id }] }; | ||
expect(api.post).toHaveBeenCalledWith('api/v3/getRecordValues', { | ||
json: request, | ||
}); | ||
expect(result).toEqual([ticket]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters