From 2a45c5bd660b149c5fe3994cee489603f7d41fdc Mon Sep 17 00:00:00 2001 From: MrOrz Date: Mon, 15 Jun 2020 02:11:04 +0800 Subject: [PATCH 1/8] Stub for article list page --- src/liff/App.svelte | 2 ++ src/liff/pages/ArticleList.svelte | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/liff/pages/ArticleList.svelte diff --git a/src/liff/App.svelte b/src/liff/App.svelte index 863a989a..d4dc6e1a 100644 --- a/src/liff/App.svelte +++ b/src/liff/App.svelte @@ -1,11 +1,13 @@ + +{#await articlesPromise} +

Loading...

+{:then resp} + +{:catch error} +

{error.message}

+{/await} \ No newline at end of file From 72fdfbe728bfe8afa44d1d522f16e8b0a8bb303d Mon Sep 17 00:00:00 2001 From: MrOrz Date: Mon, 15 Jun 2020 15:15:56 +0800 Subject: [PATCH 2/8] Rename ArticleList.svelte to Articles.svelte --- src/liff/App.svelte | 4 ++-- src/liff/pages/ArticleList.svelte | 28 ---------------------- src/liff/pages/Articles.svelte | 40 +++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 30 deletions(-) delete mode 100644 src/liff/pages/ArticleList.svelte create mode 100644 src/liff/pages/Articles.svelte diff --git a/src/liff/App.svelte b/src/liff/App.svelte index d4dc6e1a..cb1fbb8e 100644 --- a/src/liff/App.svelte +++ b/src/liff/App.svelte @@ -1,13 +1,13 @@ - -{#await articlesPromise} -

Loading...

-{:then resp} - -{:catch error} -

{error.message}

-{/await} \ No newline at end of file diff --git a/src/liff/pages/Articles.svelte b/src/liff/pages/Articles.svelte new file mode 100644 index 00000000..edfbbcb0 --- /dev/null +++ b/src/liff/pages/Articles.svelte @@ -0,0 +1,40 @@ + + +{#if articleData === null} +

Loading...

+{:else} + +{/if} \ No newline at end of file From de3ef3b17bf499baa4c910b35e3bb3c8bd629bb8 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Mon, 15 Jun 2020 15:16:35 +0800 Subject: [PATCH 3/8] Set constants required for making GraphQL to Cofacts to LIFF global --- .env.sample | 1 + src/liff/.eslintrc.js | 2 ++ webpack.config.js | 2 ++ 3 files changed, 5 insertions(+) diff --git a/.env.sample b/.env.sample index 042800e2..f4a09a57 100644 --- a/.env.sample +++ b/.env.sample @@ -22,6 +22,7 @@ ROLLBAR_CLIENT_TOKEN= # This should match rumors-api's RUMORS_LINE_BOT_SECRET APP_SECRET=CHANGE_ME +APP_ID=RUMORS_LINE_BOT # Cofacst API URL API_URL=https://cofacts-api.hacktabl.org/graphql diff --git a/src/liff/.eslintrc.js b/src/liff/.eslintrc.js index 0a84fabb..591c6347 100644 --- a/src/liff/.eslintrc.js +++ b/src/liff/.eslintrc.js @@ -30,7 +30,9 @@ module.exports = { liff: 'readonly', // Define plugin + APP_ID: 'readonly', LIFF_ID: 'readonly', DEBUG_LIFF: 'readonly', + COFACTS_API_URL: 'readonly', }, }; diff --git a/webpack.config.js b/webpack.config.js index 4d9a38f0..c0cd40da 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -128,7 +128,9 @@ module.exports = { LIFF_ID: JSON.stringify( (process.env.LIFF_URL || '').replace('https://liff.line.me/', '') ), + APP_ID: process.env.APP_ID, DEBUG_LIFF: process.env.DEBUG_LIFF, + COFACTS_API_URL: process.env.API_URL, }), ], devtool: prod ? false : 'source-map', From bd1bc50c9e16c34961a127e629c397a041feb44e Mon Sep 17 00:00:00 2001 From: MrOrz Date: Mon, 15 Jun 2020 15:20:31 +0800 Subject: [PATCH 4/8] Implement getArticlesFromCofacts() in LIFF --- src/liff/__tests__/lib.js | 97 +++++++++++++++++++++++++++++++++++++++ src/liff/lib.js | 61 ++++++++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/src/liff/__tests__/lib.js b/src/liff/__tests__/lib.js index 1446adad..79ac71ed 100644 --- a/src/liff/__tests__/lib.js +++ b/src/liff/__tests__/lib.js @@ -345,3 +345,100 @@ describe('assertSameSearchSession', () => { expect(liff.closeWindow).toHaveBeenCalledTimes(0); }); }); + +describe('getArticlesFromCofacts', () => { + let getArticlesFromCofacts; + beforeEach(() => { + global.location = { search: '?foo=bar' }; + global.COFACTS_API_URL = 'http://cofacts.api'; + global.APP_ID = 'mock_app_id'; + global.fetch = jest.fn(); + + jest.resetModules(); + getArticlesFromCofacts = require('../lib').getArticlesFromCofacts; + }); + + afterEach(() => { + delete global.COFACTS_API_URL; + delete global.fetch; + delete global.APP_ID; + delete global.location; + }); + + it('handles empty', async () => { + await expect(getArticlesFromCofacts([])).resolves.toEqual([]); + expect(fetch).not.toHaveBeenCalled(); + }); + + it('rejects on GraphQL error', async () => { + fetch.mockReturnValueOnce( + Promise.resolve({ + status: 400, + json: jest.fn().mockReturnValueOnce( + Promise.resolve({ + errors: [{ message: 'fake error' }], + }) + ), + }) + ); + + await expect(getArticlesFromCofacts(['id1'])).rejects.toMatchInlineSnapshot( + `[Error: getArticlesFromCofacts Error: fake error]` + ); + }); + + it('converts article ids to GraphQL request and returns result', async () => { + const ids = ['id1', 'id2']; + fetch.mockReturnValueOnce( + Promise.resolve({ + status: 200, + json: jest.fn().mockReturnValueOnce( + Promise.resolve({ + data: { + a0: { + id: 'id1', + text: 'text1', + }, + a1: { + id: 'id2', + text: 'text2', + }, + }, + }) + ), + }) + ); + + await expect(getArticlesFromCofacts(ids)).resolves.toMatchInlineSnapshot(` + Array [ + Object { + "id": "id1", + "text": "text1", + }, + Object { + "id": "id2", + "text": "text2", + }, + ] + `); + + // Check sent GraphQL query & variables + expect(JSON.parse(fetch.mock.calls[0][1].body)).toMatchInlineSnapshot(` + Object { + "query": " + query GetArticlesLinkedToUser( + $a0: String! + $a1: String! + ) { + a0: GetArticle(id: $a0) { text } + a1: GetArticle(id: $a1) { text } + } + ", + "variables": Object { + "a0": "id1", + "a1": "id2", + }, + } + `); + }); +}); diff --git a/src/liff/lib.js b/src/liff/lib.js index 4072e3e1..1e763baf 100644 --- a/src/liff/lib.js +++ b/src/liff/lib.js @@ -145,3 +145,64 @@ export const assertSameSearchSession = async () => { return; } }; + +/** + * @param {string[]} articleIds + * @returns {Article} Article object from Cofacts API + */ +export const getArticlesFromCofacts = async articleIds => { + if (articleIds.length === 0) return []; + + const variables = articleIds.reduce((agg, articleId, idx) => { + agg[`a${idx}`] = articleId; + return agg; + }, {}); + + const variableKeys = Object.keys(variables); + + const query = ` + query GetArticlesLinkedToUser( + ${variableKeys.map(k => `$${k}: String!`).join('\n')} + ) { + ${variableKeys + .map(k => `${k}: GetArticle(id: $${k}) { text }`) + .join('\n')} + } + `; + + let status; + return fetch(COFACTS_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-app-id': APP_ID, + }, + body: JSON.stringify({ query, variables }), + }) + .then(r => { + status = r.status; + return r.json(); + }) + .then(resp => { + if (status === 400) { + throw new Error( + `getArticlesFromCofacts Error: ${resp.errors + .map(({ message }) => message) + .join('\n')}` + ); + } + if (resp.errors) { + // When status is 200 but have error, just print them out. + console.error( + 'getArticlesFromCofacts operation contains error:', + resp.errors + ); + rollbar.error( + 'getArticlesFromCofacts error', + { body: JSON.stringify({ query, variables }) }, + { resp } + ); + } + return variableKeys.map(key => resp.data[key]); + }); +}; From 1f44d8443d70e89d084a67fffe2516f4bebe9f21 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Mon, 15 Jun 2020 15:30:05 +0800 Subject: [PATCH 5/8] Also test getArticlesFromCofacts's minor error handling --- src/liff/__tests__/lib.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/liff/__tests__/lib.js b/src/liff/__tests__/lib.js index 79ac71ed..daea9d95 100644 --- a/src/liff/__tests__/lib.js +++ b/src/liff/__tests__/lib.js @@ -353,6 +353,7 @@ describe('getArticlesFromCofacts', () => { global.COFACTS_API_URL = 'http://cofacts.api'; global.APP_ID = 'mock_app_id'; global.fetch = jest.fn(); + global.rollbar = { error: jest.fn() }; jest.resetModules(); getArticlesFromCofacts = require('../lib').getArticlesFromCofacts; @@ -363,6 +364,7 @@ describe('getArticlesFromCofacts', () => { delete global.fetch; delete global.APP_ID; delete global.location; + delete global.rollbar; }); it('handles empty', async () => { @@ -387,8 +389,8 @@ describe('getArticlesFromCofacts', () => { ); }); - it('converts article ids to GraphQL request and returns result', async () => { - const ids = ['id1', 'id2']; + it('converts article ids to GraphQL request and returns result, despite minor errors', async () => { + const ids = ['id1', 'id2', 'id3']; fetch.mockReturnValueOnce( Promise.resolve({ status: 200, @@ -404,6 +406,7 @@ describe('getArticlesFromCofacts', () => { text: 'text2', }, }, + errors: [{ message: 'Some error loading id3' }], }) ), }) @@ -419,9 +422,12 @@ describe('getArticlesFromCofacts', () => { "id": "id2", "text": "text2", }, + undefined, ] `); + expect(rollbar.error).toHaveBeenCalledTimes(1); + // Check sent GraphQL query & variables expect(JSON.parse(fetch.mock.calls[0][1].body)).toMatchInlineSnapshot(` Object { @@ -429,14 +435,17 @@ describe('getArticlesFromCofacts', () => { query GetArticlesLinkedToUser( $a0: String! $a1: String! + $a2: String! ) { a0: GetArticle(id: $a0) { text } a1: GetArticle(id: $a1) { text } + a2: GetArticle(id: $a2) { text } } ", "variables": Object { "a0": "id1", "a1": "id2", + "a2": "id3", }, } `); From bff964c00447d728270a5d4e885fc9745c0d5d3c Mon Sep 17 00:00:00 2001 From: MrOrz Date: Mon, 15 Jun 2020 17:11:36 +0800 Subject: [PATCH 6/8] Connects Cofacts API in Articles.svelte --- src/liff/lib.js | 2 +- src/liff/pages/Articles.svelte | 10 ++++++++-- webpack.config.js | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/liff/lib.js b/src/liff/lib.js index 1e763baf..b45f6e39 100644 --- a/src/liff/lib.js +++ b/src/liff/lib.js @@ -165,7 +165,7 @@ export const getArticlesFromCofacts = async articleIds => { ${variableKeys.map(k => `$${k}: String!`).join('\n')} ) { ${variableKeys - .map(k => `${k}: GetArticle(id: $${k}) { text }`) + .map(k => `${k}: GetArticle(id: $${k}) { id text }`) .join('\n')} } `; diff --git a/src/liff/pages/Articles.svelte b/src/liff/pages/Articles.svelte index edfbbcb0..b4b4c4e0 100644 --- a/src/liff/pages/Articles.svelte +++ b/src/liff/pages/Articles.svelte @@ -1,9 +1,10 @@ @@ -34,7 +40,7 @@ {:else}
    {#each articleData as link} -
  • {link.articleId} / {link.createdAt}
  • +
  • {link.articleId} / {articleMap[link.articleId] ? articleMap[link.articleId].text : 'Loading...'} / {link.createdAt}
  • {/each}
{/if} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index c0cd40da..d44b5909 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -128,9 +128,9 @@ module.exports = { LIFF_ID: JSON.stringify( (process.env.LIFF_URL || '').replace('https://liff.line.me/', '') ), - APP_ID: process.env.APP_ID, + APP_ID: JSON.stringify(process.env.APP_ID), DEBUG_LIFF: process.env.DEBUG_LIFF, - COFACTS_API_URL: process.env.API_URL, + COFACTS_API_URL: JSON.stringify(process.env.API_URL), }), ], devtool: prod ? false : 'source-map', From 936477b0d4f70b73750c94d4a7c6b250d0b96b28 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Mon, 15 Jun 2020 17:17:27 +0800 Subject: [PATCH 7/8] Fix svelte lint --- src/liff/pages/Articles.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/liff/pages/Articles.svelte b/src/liff/pages/Articles.svelte index b4b4c4e0..886fc688 100644 --- a/src/liff/pages/Articles.svelte +++ b/src/liff/pages/Articles.svelte @@ -1,6 +1,5 @@