#cofacts

2020-05-01
Nacs 10:36:15
@nacs13 has joined the channel
lucien 14:55:49
好猛
github 15:22:31
From comments in <https://github.com/cofacts/rumors-line-bot/pull/169|#169>: • `Query.context` is a little bit confusing with apollo context • `UserContext.state` &amp; `UserContext.data` should be non-nullable
github 15:34:11
*<https://github.com/cofacts/rumors-line-bot/compare/a28f1b6b5ba2...8a390abf8dac|8 new commits> pushed to <https://github.com/cofacts/rumors-line-bot/tree/dev|`dev`>* <https://github.com/cofacts/rumors-line-bot/commit/080040ad2857b3c1d410465f4ccb5a755f48caf7|`080040ad`> - GraphQL authentication &amp; context population <https://github.com/cofacts/rumors-line-bot/commit/30c8a3e52d0715a56eb6eace1484d834eb29cfe4|`30c8a3e5`> - Implement authentication, @auth directive and context field <https://github.com/cofacts/rumors-line-bot/commit/da8ccf6e33848f919e4459fbe91d3b7391052851|`da8ccf6e`> - Redundent jest.mock() as manual mocks for node modules will always load. <https://github.com/cofacts/rumors-line-bot/commit/f24191c880cf28a4fb879d64a1f440ea76071f14|`f24191c8`> - Add "manual" (auto) mock for redisClient so that all redisClient deps are mocked (such as in graphql/__tests__/insights) <https://github.com/cofacts/rumors-line-bot/commit/8dfa0c12afe6b0f6c8b49b798e4474c955450a89|`8dfa0c12`> - Prettier fix <https://github.com/cofacts/rumors-line-bot/commit/5295edb20990e761d51773539fab7903d37fc657|`5295edb2`> - Avoid calling the real redisClient in unit tests <https://github.com/cofacts/rumors-line-bot/commit/97b491de65d9a67c9c92bbdb4ac4118ff8f8359f|`97b491de`> - Add userId in GraphQL context after authentication <https://github.com/cofacts/rumors-line-bot/commit/8a390abf8dac39ad4701a9870628cbb1f06dad0c|`8a390abf`> - Merge pull request #169 from cofacts/auth-api
github 15:37:06
*<https://github.com/cofacts/rumors-line-bot/compare/8a390abf8dac...c5b684609691|9 new commits> pushed to <https://github.com/cofacts/rumors-line-bot/tree/dev|`dev`>* <https://github.com/cofacts/rumors-line-bot/commit/d36d76e47d90f391580d4ca5866aab14aa1efcf5|`d36d76e4`> - Implements mutation GraphQL endpoints for LIFF <https://github.com/cofacts/rumors-line-bot/commit/deb8bdd8075e5107db100ca2062caff5d5dccaba|`deb8bdd8`> - Remove submitReplyRequest because it is no longer needed <https://github.com/cofacts/rumors-line-bot/commit/ea856c552b8e8612936609b8fec6773c7a53d31d|`ea856c55`> - Remove submitArticle mutation because it's not needed <https://github.com/cofacts/rumors-line-bot/commit/b853bed252645822f9a81eccd79a9b72160efb64|`b853bed2`> - Prettier fix <https://github.com/cofacts/rumors-line-bot/commit/7860ec7dcc8c773ee25755d1563e26a3a4d16ac1|`7860ec7d`> - Add unit test for gql <https://github.com/cofacts/rumors-line-bot/commit/8ec314510a5e31c4de95248b3e6b09caa690a9b6|`8ec31451`> - Lint fix <https://github.com/cofacts/rumors-line-bot/commit/cc864507472181a1d2d713d18e5ecb675ac41824|`cc864507`> - gql error handling test. For test coverage... <https://github.com/cofacts/rumors-line-bot/commit/f86275a983ddd303333466dea15de103e17159ca|`f86275a9`> - Prettier fix <https://github.com/cofacts/rumors-line-bot/commit/c5b684609691bad8ec9ed6bd83df397dae310f40|`c5b68460`> - Merge pull request #170 from cofacts/mutation-api
github 15:37:22
Successfully deployed <https://github.com/cofacts/rumors-line-bot/commit/8a390abf8dac39ad4701a9870628cbb1f06dad0c|`8a390ab`> to <https://dashboard.heroku.com/apps/rumors-line-bot-staging/activity/builds/d14cc973-eef1-4425-9095-7c854349ac09|rumors-line-bot-staging>
github 15:38:01
*<https://github.com/cofacts/rumors-line-bot/compare/c5b684609691...8cc7ede5ddaf|8 new commits> pushed to <https://github.com/cofacts/rumors-line-bot/tree/dev|`dev`>* <https://github.com/cofacts/rumors-line-bot/commit/9d7f9b991f55c65a5f282ef0e11f1e0307099b1a|`9d7f9b99`> - Setup svelte liff and server build <https://github.com/cofacts/rumors-line-bot/commit/0bb6c769cdefba3d5d1589579d3fc3096c2779b7|`0bb6c769`> - audit fix <https://github.com/cofacts/rumors-line-bot/commit/51673df03e493ef42ddc49abbd3cfee1789d1396|`51673df0`> - Add liff proxy in dev mode <https://github.com/cofacts/rumors-line-bot/commit/eb8d1a7859035ff2b936544b29521d590465fbc6|`eb8d1a78`> - Add basic svelte setup <https://github.com/cofacts/rumors-line-bot/commit/0c2d09f027979d223b13077a61796da77fdc49ef|`0c2d09f0`> - Svelte i18n and corejs polyfill <https://github.com/cofacts/rumors-line-bot/commit/711fe7f2fb924332b0d94991777211c2fe3a1bfb|`711fe7f2`> - Imports polyfills that supports Safari10 and Android 52 <https://github.com/cofacts/rumors-line-bot/commit/10bb13afcf58cda3e5a09a68f61b13df3aeecd28|`10bb13af`> - Eslint ignore built liff/* <https://github.com/cofacts/rumors-line-bot/commit/8cc7ede5ddaf463e80977208ae97a93c81fa68f5|`8cc7ede5`> - Merge pull request #171 from cofacts/svelte-liff
mrorz 19:34:45
/github unsubscribe cofacts commits,statuses,deployments,releases
github 19:34:45
This channel will get notifications from <https://github.com/cofacts|cofacts> for: `issues`, `pulls`, `public`
mrorz 19:35:18
/github subscribe cofacts reviews,comments
github 19:35:18
This channel will get notifications from <https://github.com/cofacts|cofacts> for: `issues`, `pulls`, `public`, `comments`, `reviews`
mrorz 19:37:50
@ggm PR review 囉,請過目
https://github.com/cofacts/rumors-line-bot/pull/182

mongoClient 的實作我覺得有些太複雜了,abstraction 也有點怪 QQ
To see <https://github.com/cofacts/rumors-line-bot/issues/162|#162>
噢其實我覺得好像繼承 mongoClient 比較好
然後為什麼不要用 es6 class 呀
其他我晚點繼續看噢
React 寫久就會不喜歡 class (欸

其實主要是看到底想做的是 generalized client 還是 specialized singleton

我不反對做成 generalized client,就是包成 class、new 時進行連線,constructor 裡面設定 URL 等等

我也不反對另外做個 factory function 來吐一個前述的 class 的 singleton instance

但現在這個 class 實在太詭異,輸出的 constructor 可以改 URL 卻不能改 dbName,又有 singleton 的 static method、也會自動 connect 等等
我們對 `mongoClient` 還有下面的需求,會讓我覺得要滿足這些需求時,做成 class 反而是比較多餘的封裝

1. 我們會希望整個 app 共用一個 MongoDB 連線,而不是每個 request 來就重新連 MongoDB
2. 我們會連的資料庫就是 env vars 裡面指定的那些,不會有別的資料庫
這應該也是為什麼你會設計 `getInstance` 這個 static method 來回傳 singleton,因為 singleton 才是能滿足我們需求的 pattern

但如果我們想要的是 singleton 的話,那在 Javascript 的世界裡,我們大可以直接使用 object literal 就能辦到,不需要大費周章地包成 class 然後 instantiate 自己之類的
我是覺得自動連線的地方比較怪異沒錯,但其實你改成的 `getClient()` 的方式也是做了一樣的事情?「可以改 URI 卻不能改 dbName」為什麼不合理呀?我們不會用到別的 db 所以 `dbName` 不能改,但是會根據 `production` 和 `staging` 來有不同的 URI,所以 MONGODB_URI 會透過環境變數修改
主要的差別在 expose 出整個 class,跟只吐一個 factory method 的差別唷
應該是說我在裡面寫的那份 code 就是
「可以達成一樣的事情,但不會 leak 其他邏輯(規定要某種 call 法)的 abstraction」
嗯懂
但還是有隱含已連線的問題,要真的解決這個怪異,可能改成 getConnectedClient 之類的吧
然後我不覺得這是大費周章的包成 class 耶,不過就是定義一個 class 寫個 singleton ,用 object literal 的寫法不就也是模擬成 class 的樣子嗎?(吐出一個 object,然後他有一些 function 可以呼叫)那為什麼不直接用 class 來定義勒?
`getConnectedClient` 比較像是吐 object 的 factory (function) 的感覺
還是你是想說的是,既然我們都可以用 object literal 來做了,何須用 class
恩也可以這麼說
試想我們今天如果要加一上另外一個 function,你要做的事情是在 setupClient 的回傳值裡面加入一個 function
而 class 方法要做的事情,是在 class 裡面加入一個 function
我覺得 class 的做法不是比較直覺比較合理嗎?
我去找到這個 class 定義在哪裡,替他加上一個 function
我的直覺會是
我找到那個物件在哪裡,然後加上一個 method
而且 object literal 的做法,就會讓回傳值包得長長的
因為我要一包物件
他出生就是個 instance
factory 只是因為我想把連線之類的事情,從 import file 的時機點,延後到呼叫 `getClient`時做
不然其實我覺得 import 時就 connect 其實也挺好的
這樣就真的只要 `export default obj` 然後那個 `obj` 就是整個 module 的內容惹
啊其實不太行,最後至少還是要回傳個 promise
要等 connect (async) 之後才能讓使用者操作
嗯一定要回 promise
不然就是在某個地方先 await 過之類的(感覺很奇怪)
那就是 promise of 那個可以 db 呀 collection 呀 close 呀的 API (一個物件)
畢竟 JS 的 module.exports 本身就是個大 singleton
那我好奇噢,如果之後轉 typescript 的話,class 的做法可以明確的知道定義長怎樣,不管是每個 function 的或是自己,那這樣的話 object literal 的做法會長怎樣?
我需要另外定義一個 Type/Interface 來描述他是嗎?
轉 Typescript 的話
我會訂個 type 描述 `CofactsMongoClient`

然後定 getClient 會回傳 `Promise<CofactsMongoClient>`
那些 function 的 type 都指定好之後,讓 typescript 的 structural typing 確認我的 implementation 是不是確實會生出這個形狀的東西
嗯但 class 的做法不用另外再描述
對外面來說他就是吐 `Promise<CofactsMongoClient>` 的 function~
如果要 class 的話,那我覺得我們就在 constructor 裡面回傳那個 singleton instance 吧

像是這份答案的 ES7 version (Update 2019) https://stackoverflow.com/a/6733919/1582110

把 class expose 出去的時候,外面的人只能做一件事情,就是 new 它
然後 new 它,就只會回傳那個 singleton,然後連線啥的都會自動做好這樣

這樣一來外面的使用者只有一種使用方法,會是比較合適的封裝
就是 constructor 裡面會處理 getInstance or getClient 的概念
(我發現另一篇類似發問文的 best answer 就是 instance XD 但那是在 class 出現之前寫的)

https://stackoverflow.com/questions/1479319/simplest-cleanest-way-to-implement-singleton-in-javascript
好我來看看,然後把 connected 寫在註解好了,我覺得拿到一個已連線的 client 真的怪怪的 XDD 但每次呼叫 .connect() 也不是辦法
對啊沒有 class 一定是會這樣寫呀 還是 var 耶
我覺得比較棘手的部分是,我們的東西是 async 的,所以 class constructor 要怎麼處理那個 async 的部分其實滿討厭
`Downvote for the accepted answer not being a singleton at all. It's just a global variable. – mlibby Feb 25 '13 at 12:53`
對沒有原生 class 的 JS 來說
真的就是 global variable 呀 XD
對啊他好嚴格
大概是四人幫的粉絲、喜歡 Java 不喜歡 Javascript 的人 (?
> class constructor 要怎麼處理那個 async 的部分其實滿討厭
我現在想到的東西超怪的耶,就是 constructor 會回傳一個 promise,然後 promise 會 resolve to singleton instance when connected to database

所以會有一個超詭異的狀況:

我 new 一個 class ,但拿到的不是該 class 的 instance 而是 promise of that instance

超反直覺的
除非我們 extend Promise (更鬧了)
要不然就是保留現在的 getInstance

未來 Typescript 的時候直接用 private constructor 把 new 那條路堵起來,這樣只能呼叫 `getInstance` 抓 instance

https://khalilstemmler.com/blogs/typescript/when-to-use-a-private-constructor/
extend Promise 很帥耶⋯
我不太能接受 extend Promise

我覺得 private constructor 是最接近你原本想做的東西了
現在沒有 typescript
就放個註解說「不要 new!」吧
嗯 typescript 的話就可以 private constructor
啊然後其實 dbName 也可以拿掉,用預設的,也就是 MONGODB_URI 的
喔喔那個 mongodb connection uri `/` 後面帶的 db name 是 auth 用 db,不一定等於資料存放的 db 的說
`/` 後面是 dbname 沒錯, `authsource` 可以指定另外的 auth
欸不是
所以就算有帶 `/dbName` 我們還是要 `db(dbName)` 一次
我要說的是可以這樣 `/cofacts?authsource=admin`
嗯但這樣連線完,你似乎還是得要 `.db('cofacts')` ?
可以試試看
我的印象是還是要 `db` 指定 db name
不用噢
The name of the database we want to use. If not provided, use database name from connection string.
真的耶
學到了
ggm 19:51:11
好啊我看看
ggm 20:30:03
噢其實我覺得好像繼承 mongoClient 比較好
ggm 20:31:12
然後為什麼不要用 es6 class 呀
ggm 20:32:10
其他我晚點繼續看噢
mrorz 22:38:24
React 寫久就會不喜歡 class (欸

其實主要是看到底想做的是 generalized client 還是 specialized singleton

我不反對做成 generalized client,就是包成 class、new 時進行連線,constructor 裡面設定 URL 等等

我也不反對另外做個 factory function 來吐一個前述的 class 的 singleton instance

但現在這個 class 實在太詭異,輸出的 constructor 可以改 URL 卻不能改 dbName,又有 singleton 的 static method、也會自動 connect 等等
mrorz 22:45:42
我們對 `mongoClient` 還有下面的需求,會讓我覺得要滿足這些需求時,做成 class 反而是比較多餘的封裝

1. 我們會希望整個 app 共用一個 MongoDB 連線,而不是每個 request 來就重新連 MongoDB
2. 我們會連的資料庫就是 env vars 裡面指定的那些,不會有別的資料庫
這應該也是為什麼你會設計 `getInstance` 這個 static method 來回傳 singleton,因為 singleton 才是能滿足我們需求的 pattern

但如果我們想要的是 singleton 的話,那在 Javascript 的世界裡,我們大可以直接使用 object literal 就能辦到,不需要大費周章地包成 class 然後 instantiate 自己之類的
ggm 23:07:12
我是覺得自動連線的地方比較怪異沒錯,但其實你改成的 `getClient()` 的方式也是做了一樣的事情?「可以改 URI 卻不能改 dbName」為什麼不合理呀?我們不會用到別的 db 所以 `dbName` 不能改,但是會根據 `production` 和 `staging` 來有不同的 URI,所以 MONGODB_URI 會透過環境變數修改
  • 👍1
mrorz 23:21:54
主要的差別在 expose 出整個 class,跟只吐一個 factory method 的差別唷
mrorz 23:24:49
應該是說我在裡面寫的那份 code 就是
「可以達成一樣的事情,但不會 leak 其他邏輯(規定要某種 call 法)的 abstraction」
ggm 23:25:32
嗯懂
ggm 23:28:34
但還是有隱含已連線的問題,要真的解決這個怪異,可能改成 getConnectedClient 之類的吧
ggm 23:30:00
然後我不覺得這是大費周章的包成 class 耶,不過就是定義一個 class 寫個 singleton ,用 object literal 的寫法不就也是模擬成 class 的樣子嗎?(吐出一個 object,然後他有一些 function 可以呼叫)那為什麼不直接用 class 來定義勒?
mrorz 23:30:46
`getConnectedClient` 比較像是吐 object 的 factory (function) 的感覺
ggm 23:30:50
還是你是想說的是,既然我們都可以用 object literal 來做了,何須用 class
mrorz 23:31:08
恩也可以這麼說
ggm 23:31:23
試想我們今天如果要加一上另外一個 function,你要做的事情是在 setupClient 的回傳值裡面加入一個 function
ggm 23:31:42
而 class 方法要做的事情,是在 class 裡面加入一個 function
ggm 23:32:00
我覺得 class 的做法不是比較直覺比較合理嗎?
ggm 23:32:23
我去找到這個 class 定義在哪裡,替他加上一個 function
mrorz 23:33:07
我的直覺會是
我找到那個物件在哪裡,然後加上一個 method
ggm 23:33:12
而且 object literal 的做法,就會讓回傳值包得長長的
mrorz 23:33:34
因為我要一包物件
他出生就是個 instance
mrorz 23:34:32
factory 只是因為我想把連線之類的事情,從 import file 的時機點,延後到呼叫 `getClient`時做
不然其實我覺得 import 時就 connect 其實也挺好的
mrorz 23:35:04
這樣就真的只要 `export default obj` 然後那個 `obj` 就是整個 module 的內容惹
mrorz 23:36:58
啊其實不太行,最後至少還是要回傳個 promise
要等 connect (async) 之後才能讓使用者操作
ggm 23:37:57
嗯一定要回 promise
ggm 23:38:15
不然就是在某個地方先 await 過之類的(感覺很奇怪)
mrorz 23:38:37
那就是 promise of 那個可以 db 呀 collection 呀 close 呀的 API (一個物件)
mrorz 23:42:44
畢竟 JS 的 module.exports 本身就是個大 singleton
ggm 23:43:11
那我好奇噢,如果之後轉 typescript 的話,class 的做法可以明確的知道定義長怎樣,不管是每個 function 的或是自己,那這樣的話 object literal 的做法會長怎樣?
ggm 23:44:30
我需要另外定義一個 Type/Interface 來描述他是嗎?
mrorz 23:45:32
轉 Typescript 的話
我會訂個 type 描述 `CofactsMongoClient`

然後定 getClient 會回傳 `Promise<CofactsMongoClient>`
mrorz 23:46:17
那些 function 的 type 都指定好之後,讓 typescript 的 structural typing 確認我的 implementation 是不是確實會生出這個形狀的東西
ggm 23:46:24
嗯但 class 的做法不用另外再描述
  • 1
  • 1
mrorz 23:46:43
對外面來說他就是吐 `Promise<CofactsMongoClient>` 的 function~
mrorz 23:50:50
如果要 class 的話,那我覺得我們就在 constructor 裡面回傳那個 singleton instance 吧

像是這份答案的 ES7 version (Update 2019) https://stackoverflow.com/a/6733919/1582110

把 class expose 出去的時候,外面的人只能做一件事情,就是 new 它
然後 new 它,就只會回傳那個 singleton,然後連線啥的都會自動做好這樣

這樣一來外面的使用者只有一種使用方法,會是比較合適的封裝
Stack Overflow
Possible Duplicate: Simplest/Cleanest way to implement singleton in JavaScript? I'm using this pattern for singletons, in the example the singleton is PlanetEarth: var NAMESPACE = function ()...
mrorz 23:51:14
就是 constructor 裡面會處理 getInstance or getClient 的概念
mrorz 23:54:24
(我發現另一篇類似發問文的 best answer 就是 instance XD 但那是在 class 出現之前寫的)

https://stackoverflow.com/questions/1479319/simplest-cleanest-way-to-implement-singleton-in-javascript
Stack Overflow
What is the simplest/cleanest way to implement singleton pattern in JavaScript?
ggm 23:54:36
好我來看看,然後把 connected 寫在註解好了,我覺得拿到一個已連線的 client 真的怪怪的 XDD 但每次呼叫 .connect() 也不是辦法
ggm 23:55:41
對啊沒有 class 一定是會這樣寫呀 還是 var 耶
mrorz 23:56:21
我覺得比較棘手的部分是,我們的東西是 async 的,所以 class constructor 要怎麼處理那個 async 的部分其實滿討厭
  • 👍1
ggm 23:56:30
`Downvote for the accepted answer not being a singleton at all. It's just a global variable. – mlibby Feb 25 '13 at 12:53`
ggm 23:56:31
XDD
mrorz 23:57:21
對沒有原生 class 的 JS 來說
真的就是 global variable 呀 XD
ggm 23:57:39
對啊他好嚴格
mrorz 23:58:15
大概是四人幫的粉絲、喜歡 Java 不喜歡 Javascript 的人 (?
2020-05-02
mrorz 00:01:31
> class constructor 要怎麼處理那個 async 的部分其實滿討厭
我現在想到的東西超怪的耶,就是 constructor 會回傳一個 promise,然後 promise 會 resolve to singleton instance when connected to database

所以會有一個超詭異的狀況:

我 new 一個 class ,但拿到的不是該 class 的 instance 而是 promise of that instance

超反直覺的
mrorz 00:02:11
除非我們 extend Promise (更鬧了)
mrorz 00:11:59
要不然就是保留現在的 getInstance

未來 Typescript 的時候直接用 private constructor 把 new 那條路堵起來,這樣只能呼叫 `getInstance` 抓 instance

https://khalilstemmler.com/blogs/typescript/when-to-use-a-private-constructor/
khalilstemmler.com
In this blog post, I explain how using a private constructor helps to force a single way to create an object, and why it's most commonly used with the Factory Pattern.
ggm 00:14:15
extend Promise 很帥耶⋯
mrorz 00:14:40
我不太能接受 extend Promise

我覺得 private constructor 是最接近你原本想做的東西了
mrorz 00:14:45
現在沒有 typescript
mrorz 00:14:56
就放個註解說「不要 new!」吧
  • 1
ggm 00:15:41
嗯 typescript 的話就可以 private constructor
ggm 00:15:45
ggm 00:22:50
啊然後其實 dbName 也可以拿掉,用預設的,也就是 MONGODB_URI 的
github 00:25:15
Update: as <https://g0v-slack-archive.g0v.ronny.tw/index/channel/C2PPMRQGP/2020-05|discussed in Slack> we decided to add a comment to constructor to indicate that the users of `CofactsMongoClient` should never call `new` by themselves. When we adopt Typescript in the future, we will make the constructor private to make sure that users of `CofactsMongoClient` can only retrieve its singleton instance via `getInstance()` static method. `CofactsMongoClient` is dedicated to connecting to Cofacts LINE bot mongodb, not something that is generalized. ES6 class is used so that it's easier to define method and update the client type in the same time, also ensures a smoother updater when we rewrite in Typescript.
mrorz 00:25:53
喔喔那個 mongodb connection uri `/` 後面帶的 db name 是 auth 用 db,不一定等於資料存放的 db 的說
ggm 00:26:48
`/` 後面是 dbname 沒錯, `authsource` 可以指定另外的 auth
ggm 00:27:36
欸不是
mrorz 00:27:38
所以就算有帶 `/dbName` 我們還是要 `db(dbName)` 一次
ggm 00:27:41
我要說的是可以這樣 `/cofacts?authsource=admin`
mrorz 00:28:09
嗯但這樣連線完,你似乎還是得要 `.db('cofacts')` ?
mrorz 00:28:44
可以試試看
我的印象是還是要 `db` 指定 db name
ggm 00:29:15
不用噢
ggm 00:29:25
The name of the database we want to use. If not provided, use database name from connection string.
mrorz 00:31:07
真的耶
mrorz 00:31:08
學到了
mrorz 00:31:09
  • 🙌1
mrorz 01:11:54
Replied to a thread: 2020-04-22 22:43:20
這份在 review 中的 UI,目前放上 staging 囉
https://cofacts.hacktabl.org/

其中 reply list 會遇到之前 user 是 null 的問題
cofacts.hacktabl.org
Cofacts is a collaborative system connecting instant messages and fact-check reports or different opinions together. It’s a grass-root effort fighting mis/disinformation in Taiwan.
mrorz 01:14:21
但那應該是因為有 user 的 app id 是 `DEVELOPMENT_BACKEND` 的關係。因此,其實 reply 或任何東西的 user 都有可能是 null,程式邏輯上不能有任何地方沒有先檢查 user 不是 null 就直接存取 user 唷。
cc/ @yanglin5689446
mrorz 02:25:35
Staging 上某 reply 的 user 是 null
image.png
mrorz 02:26:07
localhost 上則不是 null
image.png
mrorz 02:30:34
這其實是當時為了讓 localhost 開發方便,
訂有讓 localhost 可以看到 app_id = `DEVEOPMENT_BACKEND` 的使用者的緣故
https://github.com/cofacts/rumors-api/blob/master/src/graphql/models/User.js#L113-L115

其實只要是 localhost:3000 接上 API server 之後,就能打 mutation,這其實不太安全,所以才做了這種 `DEVELOPMENT_BACKEND` 機制,特別允許這種 localhost:3000 的 request,只是安上不同 appId https://github.com/cofacts/rumors-api/blob/master/src/checkHeaders.js#L37-L42
mrorz 02:33:53
而 staging 上那個造成 exception 的 reply,app_id 確實是 `DEVELOPMENT_BACKEND` @@
```{
"_index" : "replies_v1_0_2",
"_type" : "doc",
"_id" : "jD2sy3EBrIRcahlYXQri",
"_version" : 2,
"found" : true,
"_source" : {
"userId" : "hD1qunEBrIRcahlYYQoi",
"appId" : "DEVELOPMENT_FRONTEND",
"type" : "NOT_ARTICLE",
"text" : "fasdfasdfadsfasdfadsfasdfadsfadsfadsfadsfadsfadsfadsfasdffasdfasdfadsfasdfadsfasdfadsfadsfadsfadsfadsfadsfadsfasdffasdfasdfadsfasdfadsfasdfadsfadsfadsfadsfadsfadsfadsfasdf",
"reference" : "fasdfasdfadsfasdfadsfasdfadsfadsfadsfadsfadsfadsfadsfasdf",
"createdAt" : "2020-04-30T15:20:03.289Z",
"hyperlinks" : [ ]
}
}```
mrorz 02:38:52
還是其實我應該做的是在 staging 開放 localhost 但 production 不開放

把 `DEVELOPMENT_BACKEND` 機制整個淘汰掉
以及把資料庫內所有 appId = `DEVELOPMENT_BACKEND` 的東西都砍掉?
mrorz 02:39:24
但這樣就無法模擬有不同 appId 的狀況就是了 QQ
github 12:29:52
Merge base temporarily changed to new page UI for correct diff. Will change back to dev benefits merge.
2020-05-03
github 04:26:48
Seems that in staging site, `user` being `null` would break `Avatar` and throw exception. I think the implementation of `&lt;Avatar&gt;` should guard against `null`.
github 04:26:48
Mobile menu has the same z-index with result dropdown but comes later in DOM, thus it is coming through: <https://user-images.githubusercontent.com/108608/80872659-d78d9100-8ce5-11ea-87e3-61657e99221d.gif|z-index> May need to increase z-index here, or form a stacking context with `z-index: 0` for mobile menu to contain the badge?
github 04:26:48
As discussed <https://g0v-tw.slack.com/archives/C2PPMRQGP/p1587274861203800|in Slack>, please don't use `withData` in non-page components. Just use them on components under `pages/` directory.
github 04:26:48
Seems that the padding is too large in mobile version, names and avatar do not align in center. <https://user-images.githubusercontent.com/108608/80833043-5ddf9f80-8c20-11ea-83e7-e8e3722f4d24.png|image> You can use object / array notation on `py` for setting this responsively <https://material-ui.com/system/basics/#object|https://material-ui.com/system/basics/#object>
github 04:26:48
Inconsistent size of thumb-up/down button and the reason button in mobile view. <https://user-images.githubusercontent.com/108608/80891170-f5aebd80-8cf4-11ea-8163-818ee6de925a.png|image> Corresponding mockup: <https://user-images.githubusercontent.com/108608/80891197-1f67e480-8cf5-11ea-851f-39bc14f9c875.png|image>
github 04:26:48
I think we can open another ticket for implementing the real "for you" count listed in spec: <https://g0v.hackmd.io/iJm9_nZaTA2GyInn7ycxoA|https://g0v.hackmd.io/iJm9_nZaTA2GyInn7ycxoA> The "自己沒有送過任何 normal articleReply" part has no API yet, while "article.replyRequestCount &gt;= N (default N = 2)" and "沒有任何 normal articleReply 符合「正feedback&gt;負feedback &amp;&amp; status == "NORMAL"」" are currently available in `ListArticle` filter.
github 04:26:49
Search result for replies is different from mockup. It should be listing replies that has the query text, rather than articles that has the query text. Mockup: <https://user-images.githubusercontent.com/108608/80891388-3a872400-8cf6-11ea-9ecc-dfd781fd27fc.png|image> Current: <https://user-images.githubusercontent.com/108608/80891392-4377f580-8cf6-11ea-99c0-fae928ed5f47.png|image> This may be huge so can include in future PRs.
github 04:26:49
You also need `vote` under each feedbacks, because the you are using `vote` to move feedbacks in the right tab. Currently both tabs are empty because no feedback has `vote` field...... <https://user-images.githubusercontent.com/108608/80891094-61445b00-8cf4-11ea-82a2-eda5e2b83054.png|image>
mrorz 14:10:48
https://github.com/cofacts/rumors-site/pull/248/files
@yanglin5689446 是說這個是不是還沒 ready 呀

我看用到 `ExpandableText` 的地方傳了 `lineClamp` 進去,但 `ExpandableText` 內部並沒有處理它
mrorz 16:22:45
Replied to a thread: 2020-04-16 16:26:26
關於「我查核過」的 filter
他跟 navbar 「等你來答」的數字的 query
定義剛好相反
(有我的 articleReply 與沒有我的 articleReply)
g0v.hackmd.io
mrorz 16:24:58
我在想要不要比照 `hasPositiveFeedbackArticleReply` ,在 `ListArticle` filter做一個 `hasMyArticleReply`

`true` 就是有,`false`就是沒有

不過之後 profile page 如果要列出某使用者的 `articleReply`,不知道是要改 `ListArticle` filter 還是要在 User 底下加 field 就是了⋯⋯

cc/ @lucien @yanglin5689446
yanglin 16:28:01
如果有座在 `ListArticle` 在 profile 就繼續用就可以了吧?
反正 query 會被 group 在一起發送?
mrorz 16:28:03
如果「某使用者的 `articleReply`」只會在 profile 之類的頁面使用,那確實是做在 User 底下比較合理,這樣 `ListArticle` filter 只要實作「有/沒有我的 articleReply」filter 即可
github 16:39:13
<https://github.com/orgs/cofacts/projects/5#card-37456782|https://github.com/orgs/cofacts/projects/5#card-37456782> added here
github 16:40:31
card added here: <https://github.com/orgs/cofacts/projects/5#card-34990256|https://github.com/orgs/cofacts/projects/5#card-34990256>
github 19:24:37
You may reset `MockDate` here
2020-05-04
github 00:49:29
*PR has been edited* :construction_worker: This PR has received other commits, so Renovate will stop updating it to avoid conflicts or other problems. If you wish to abandon your changes and have Renovate start over you may click the "rebase" checkbox in the PR body/description.
github 00:59:54
<https://coveralls.io/builds/30522585|Coverage Status> Coverage decreased (-5.1%) to 85.901% when pulling *<https://github.com/cofacts/rumors-api/commit/a64cc97a8f82f0c4bcd3b9f5946f4b55feb91ea9|a64cc97> on renovate/all* into *<https://github.com/cofacts/rumors-api/commit/4e8354b1aed320fc76cdeb09a4c1468822e09956|4e8354b> on master*.
github 01:13:01
The Elasticsearch js library we are <https://www.npmjs.com/package/elasticsearch|currently using> is deprecated, thus we are migrating to the <https://www.elastic.co/blog/new-elasticsearch-javascript-client-released|new client>. 95% of the changes in this PR is to mitigate this <https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/6.x/breaking-changes.html|breaking change>: &gt; The returned value of an API call will no longer be the body, statusCode, and headers for callbacks and just the body for promises. The new returned value will be a unique object containing the body, statusCode, headers, warnings, and meta, for both callback and promises. Therefore a majority of the code change is destructuring `body` from the all the values the elasticsearch client returns. We remove elasticsearch log related setup, since the new client does not have an integrated logger anymore. We add unit test to `auth.js`, in which some of its logic binding to passport cannot be tested, thus the coverage goes down a bit. Lastly, the version of submodule rumors-db is also updated. In latest changes, `.env` file is removed from rumors-db codebase, thus we need to provide ELASTICSEARCH_URL manually in package.json of rumors-api, otherwise it will break environments like Travis CI.
因為原本的 elasticsearch js client deprecate 了,我更新了一下 elasticsearch js client。

他有個大 breaking change,更正了 client 回傳的所有結構,所以導致 rumors-api 裡面每一個有呼叫到 elasticsearch client 的地方幾乎都要跟著改。總之這個 PR 之所以巨大就是大在這囧
這個變更太多了,為了找出有沒有問題
我先把它放到 staging 上跑跑看囉

如果大家最近打 staging API 出問題請提出,說不定是這個 PR 的問題
@darkbtf 留意個
mrorz 01:18:23
因為原本的 elasticsearch js client deprecate 了,我更新了一下 elasticsearch js client。

他有個大 breaking change,更正了 client 回傳的所有結構,所以導致 rumors-api 裡面每一個有呼叫到 elasticsearch client 的地方幾乎都要跟著改。總之這個 PR 之所以巨大就是大在這囧
  • 😱1
mrorz 01:20:19
@yanglin5689446
其實主要是考量 `ListArticle` 這裡如果計算 `articleReply` 還要考慮是「哪個使用者發的 `articleReply`」那 API 就不能只是 boolean
而必須是個 boolean + user ID string,所以會從
```hasMyArticleReply: Boolean```
變成
```articleReplyFrom: { userId: String, appId: String, exists: Boolean }```
(想不到更好的取名 囧)
  • 👍1
github 01:32:56
其實我是指左邊兩顆 vote 按鈕,與右邊灰底的顯示部分不一樣高 <https://user-images.githubusercontent.com/108608/80921136-0ddf1500-8da7-11ea-9607-e723cb5b942d.png|image> Mockup 裡,雖然 mobile / desktop 下的 button 高度不同,但左右兩塊都是等高的。
github 02:03:01
mobile 下, avatar 應該要跟右側文字置中。目前這樣好像有點太低了 <https://user-images.githubusercontent.com/108608/80921817-3ff27600-8dab-11ea-959e-14a0224b806d.png|image>
github 03:41:26
Actually, replies page has different filter from article page. Mockup: <https://user-images.githubusercontent.com/108608/80923827-88646080-8db8-11ea-8dea-faf365456f67.png|image> Spec: <https://g0v.hackmd.io/ZZWHWi2BTuyhkSWzAvKLAw#%E9%81%8E%E6%BF%BE%E9%81%B8%E9%A0%85%EF%BC%9A|https://g0v.hackmd.io/ZZWHWi2BTuyhkSWzAvKLAw#%E9%81%8E%E6%BF%BE%E9%81%B8%E9%A0%85%EF%BC%9A> The API for "我查核過" and reply type filtering is still on the way, but "還未有有效查核", "熱門回報" and "熱門討論" are legit `ListArticle` filters already (`hasPositiveFeedbackArticleReply`, `replyRequestCount` and `replyCount` respectively). Considering the size of this change, I think we can handle this in future PRs, may need a ticket for this.
github 10:22:18
card added here: <https://github.com/orgs/cofacts/projects/5#card-37494391|https://github.com/orgs/cofacts/projects/5#card-37494391>
github 14:03:41
LGTM! Let's ship it to staging <https://github.githubassets.com/images/icons/emoji/shipit.png|:shipit:>
  • 1
  • ❤️1
Deployed to staging 🚀
mrorz 16:25:49
Replied to a thread: 2020-05-04 14:03:41
Deployed to staging 🚀
yanglin 19:09:54
是說現在的 category 是透過一個環境變數決定要不要顯示
不過現在 category 功能應該已經測試過了?
可以打開了嗎?
mrorz 19:54:45
被藏起來的是 add category 的功能
我覺得應該要藏到有訊息被 category 分好類為止(也就是資料庫裡會有一些訊息是分好類的),這樣使用者就能用這些當成例子,判斷手頭上還沒有被分類的某訊息是屬於哪一類。

分類的進度要問 @ggm

我覺得新寫介面的話就不用考慮那個 env var 了
2020-05-05
github 02:12:43
Current implementation (puppeteer) eats up too much resource and the its throughput cannot keep up with the usage of rumors-api. We should consider other solutions as well. *Resources* Previous research: <https://github.com/cofacts/rumors-api/issues/41|cofacts/rumors-api#41> Web archiving tools by IIPC (behind wayback machine) <https://github.com/iipc/awesome-web-archiving|https://github.com/iipc/awesome-web-archiving> *Archiver* • Python + headless chrome <https://github.com/internetarchive/brozzler|https://github.com/internetarchive/brozzler> • Headless chrome <https://github.com/N0taN3rd/Squidwarc|https://github.com/N0taN3rd/Squidwarc> • Headless chrome with queue <https://github.com/yujiosaka/headless-chrome-crawler|https://github.com/yujiosaka/headless-chrome-crawler> • Save all in one html file <https://github.com/Y2Z/monolith|https://github.com/Y2Z/monolith> *Text summarization (w/ Chinese upport)* • request + newspaper3k (Python3) <https://newspaper.readthedocs.io/en/latest/|https://newspaper.readthedocs.io/en/latest/> *Misc* • Readability + readability-readarable <https://github.com/mozilla/readability#whats-readability-readerable|https://github.com/mozilla/readability#whats-readability-readerable>
yanglin 10:53:44
@mrorz 我昨天又重新用上面討論的方式實作了
之前那個算是丟上來討論而已
  • 1
yanglin 10:54:11
再麻煩你幫我 review 了🙏
chihao 14:54:53
有個關於 cofacts collected messages 的問題,如果想知道含有特定關鍵字的 collected message,推薦在這裡搜尋嗎? https://cofacts.g0v.tw/articles?filter=all 在這裡搜尋可以按照時間排序嗎?
1. 可以在搜尋框搜尋無誤唷!不過如果文字長一點,會有不一樣的效果。
2. 現在 UI 沒有,但之後搜尋介面下可以按照其他方式排序,也可以指定要哪個時間送進來的資料。
mrorz++
這個部分是 @yanglin5689446 實作的 ~~
感謝 🙌 有其他搜尋的管道嗎?我記得好像有 API 但還沒用過 😅
Chatbot 就是搜尋管道之一
只是 Chatbot 比對比較嚴格,差太多就會說找不到
網站上的搜尋功能,設定寬鬆很多,擦邊都算相似
GraphQL API 是 ListArticles ,filter.moreLikeThis
ggm 15:05:25
30k 篇的分類都分好了,隨時可以打回去 production
ggm 15:13:01
我其實忘記現在 category mutation API 是不是上 production 了,上了的話 @darkbtf 就可以把我們現在標的好的資料含若水標記的資料全部打回去哩
應該是有在 production 上
我覺得可以先把資料放進 Staging 測測
恩恩我們是預計這禮拜會打到 staging
但是 staging 和 production 的文章好像長得不一樣
就能打就打(?
嗯嗯
就是 staging 不存在的文章就跳過~
mrorz 15:21:02
1. 可以在搜尋框搜尋無誤唷!不過如果文字長一點,會有不一樣的效果。
2. 現在 UI 沒有,但之後搜尋介面下可以按照其他方式排序,也可以指定要哪個時間送進來的資料。
chihao 15:21:36
mrorz++
mrorz 15:22:04
這個部分是 @yanglin5689446 實作的 ~~
mrorz 15:22:33
chihao 15:22:36
感謝 🙌 有其他搜尋的管道嗎?我記得好像有 API 但還沒用過 😅
mrorz 15:23:24
Chatbot 就是搜尋管道之一
只是 Chatbot 比對比較嚴格,差太多就會說找不到
mrorz 15:23:45
網站上的搜尋功能,設定寬鬆很多,擦邊都算相似
mrorz 15:24:18
GraphQL API 是 ListArticles ,filter.moreLikeThis
mrorz 15:27:30
應該是有在 production 上
我覺得可以先把資料放進 Staging 測測
ggm 15:28:09
恩恩我們是預計這禮拜會打到 staging
ggm 15:28:26
但是 staging 和 production 的文章好像長得不一樣
ggm 15:28:43
就能打就打(?
yanglin 15:59:41
不知道為什麼 `GetArticle` 只要 query `requestedForReply` 就會變得很慢欸 0.0
會不會是因為你 get 的那篇 article 的 reply request 太多呀
咦不對呀
他用 userId + articleId 下去搜的話,應該只會找到 0 個或 1 個 @@
requestedForReply 應該只是一個布林?
他是即時去搜 replyrequest
了解
我只要加這個 field 就會一直轉圈圈 XD
他是「這個使用者有沒有按過我也想知道」
所以每個人不一樣~
但不應該要轉很久才對
啊除非
你是在文章列表裡面下 `requestedForReply`
我是在 `GetArticle` 下

應該不會慢很多才對,就是多一個到 elasticsearch 撈資料的動作而已 @@
今天稍微調查了一下發現
只要加了 `requestedForReply`
Hyperlink 的 polling 就會沒有 response
導致 app 一直轉圈圈
怪了,為什麼會跟 hyperlink 有關 QQ
我也不太確定 我之後再試試看
我現在是在 Article details 的頁面的 GetArticle query 新增 就會
我發現 5/4 更新 staging API 成 https://github.com/cofacts/rumors-api/pull/153 的版本之後,5/5 @yanglin5689446 就回報問題

朝 API 裡面 elasticsearch result 沒接好的方向調查 ing
結果不是 elasticsearch result 沒接好
而是
1. 一般來說,跟使用者相關的 object field 如 `ownVote` 等等,在沒有指定 userId (如:SSR) 時,會直接回傳 null。然而, `requestedForReply` 錯誤地使用了只有 mutation API 該用的 `assertUser({...})` ,所以只要有 requestedForReply 就會產生 error。
2. 我們用的 apollo-link-error 如果有 exception 的話,他就會無聲無息地悶不吭聲,讓後面的 apollo-client resolve 一整個卡住。我是在 `BatchHttpLink` 與 `onError` 中間插了一個印 console 的 link 才能抓到那個 error。

目前我們的 `onError` 裡頭用了 `alert()`,在 NodeJS 環境下並無此 function,所以他其實應該在 runtime 時遇到了 Reference error,但卻無聲無息。
至於 apollo-link-error 把 runtime error 吃掉這件事情,我在 codesandbox 上卻無法重現

https://codesandbox.io/s/awesome-shamir-vpf2d?file=/index.js
看起來 error 有好好地被抓出來 @@
mrorz 16:45:30
嗯嗯
就是 staging 不存在的文章就跳過~
mrorz 16:46:42
會不會是因為你 get 的那篇 article 的 reply request 太多呀
mrorz 16:47:56
咦不對呀
他用 userId + articleId 下去搜的話,應該只會找到 0 個或 1 個 @@
yanglin 16:49:29
requestedForReply 應該只是一個布林?
mrorz 16:49:52
他是即時去搜 replyrequest
mrorz 16:50:13
``` requestedForReply: { type: GraphQLBoolean, description: 'If the current user has requested for reply for this article', resolve: async ({ id }, args, { userId, appId, loaders }) =&gt; { assertUser({ userId, appId }); const userReplyRequests = await loaders.searchResultLoader.load({ index: 'replyrequests', body: { query: { bool: { must: [{ term: { userId } }, { term: { articleId: id } }], }, }, }, }); return userReplyRequests &amp;&amp; userReplyRequests.length &gt; 0; }, }, ```
yanglin 16:50:23
了解
yanglin 16:50:39
我只要加這個 field 就會一直轉圈圈 XD
mrorz 16:50:41
他是「這個使用者有沒有按過我也想知道」
所以每個人不一樣~
mrorz 16:50:54
但不應該要轉很久才對
mrorz 16:51:17
啊除非
你是在文章列表裡面下 `requestedForReply`
yanglin 16:51:42
我是在 `GetArticle` 下
mrorz 16:52:07

應該不會慢很多才對,就是多一個到 elasticsearch 撈資料的動作而已 @@
stbb1025 17:11:03
@lucien @mrorz發佈查核闢謠的時候,資料來源是一定要附上的嗎?還是可以空白?或是不硬性規定但平台這邊會希望使用者附上資料來源?
只有「不在查證範圍」可以不用附來源,否則原則上是一定要附。

但過去也有些人亂打哎哎
我覺得我們可以實驗看看改成一定要附上
就算是他自己打電話去確認,他也要附註他自己去電話查核的時間跟紀錄,所以應該會有文字記錄
這樣我mobile比較好設計
不在查證範圍 是一定沒有來源欄位的
比較有爭議是個人意見,到底要不要給來源
個人意見找反例有時候會非常困難
我覺得個人意見還是要有欄位讓他填「不同意見」

就算只是佐證自己在回應裡講的材料也好

只是這就不是嚴格的「不同意見出處」
mrorz 17:12:15
只有「不在查證範圍」可以不用附來源,否則原則上是一定要附。

但過去也有些人亂打哎哎
lucien 17:24:29
我覺得我們可以實驗看看改成一定要附上
lucien 17:25:51
就算是他自己打電話去確認,他也要附註他自己去電話查核的時間跟紀錄,所以應該會有文字記錄
  • 👍1
stbb1025 17:28:54
stbb1025 17:29:14
這樣我mobile比較好設計
lucien 17:29:35
不在查證範圍 是一定沒有來源欄位的
lucien 17:29:56
比較有爭議是個人意見,到底要不要給來源
lucien 17:30:14
個人意見找反例有時候會非常困難
mrorz 17:32:07
我覺得個人意見還是要有欄位讓他填「不同意見」

就算只是佐證自己在回應裡講的材料也好

只是這就不是嚴格的「不同意見出處」
github 22:11:51
We can use this so that the translators don't need to handle the triangles: ``` ({expanded ? t`Show Less` + ' ▲' : t`Show More` + ' ▼'}) ``` or ``` ({expanded ? `${t`Show Less`} ▲` : `${t`Show More`} ▼`}) ```
github 22:38:00
Not sure why, but there are some weird cases that need to trace code to figure out why <https://user-images.githubusercontent.com/108608/81078431-cf6d6580-8f20-11ea-9f84-6179c4ba4342.png|image> The article content is `555`. Can be found by searching `555` in page. Other similar scenario are: <https://user-images.githubusercontent.com/108608/81078509-ec099d80-8f20-11ea-8040-4ee1f7cccce0.png|image> <https://user-images.githubusercontent.com/108608/81078549-fd52aa00-8f20-11ea-94b4-6f2fef72861e.png|image>
github 23:02:05
Blocked by <https://github.com/storybookjs/storybook/issues/10631|storybookjs/storybook#10631>
github 23:12:40
I suggest adding the component to storybook so that we can get visual documentation &amp; snapshot testing
2020-05-06
mrorz 00:16:01
請問 @yanglin5689446 今天晚上有空線上會議嗎~

今天晚上 @stbb1025 會與 @lucien 討論 UI (detail page mobile 版),也想問問你有沒有什麼想當面問的

時間大概會是 @lucien 到場的時間(8pm~11pm 之間的不知道什麼時間)
可以喔
今天我可以線上會議嗎?
我大概九點
開始叫我唷
我正在使用 tico
毫無反應
@lucien 你不見了
github 01:16:18
Update: 1. abort LIFF on desktop <https://camo.githubusercontent.com/425d8a29fc7b947ffbbc9ca790a714c00c0ee8eb/68747470733a2f2f73332d61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f6730762d6861636b6d642d696d616765732f75706c6f6164732f75706c6f61645f32353230343736353734383931643233303762643131343533326165323237372e676966|https://camo.githubusercontent.com/425d8a29fc7b947ffbbc9ca790a714c00c0ee8eb/68747470733a2f2f73332d61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f6730762d6861636b6d642d696d616765732f75706c6f6164732f75706c6f61645f32353230343736353734383931643233303762643131343533326165323237372e676966> 2. explicitly tell user that we have recorded their feedback (regardless of them inputting the reason or not) in LIFF <https://camo.githubusercontent.com/59a3074d175f7d205d2e1eafaacb6c92f2a04171/68747470733a2f2f73332d61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f6730762d6861636b6d642d696d616765732f75706c6f6164732f75706c6f61645f62313061303365653566333061623433323163323437386161316634663735342e706e67|https://camo.githubusercontent.com/59a3074d175f7d205d2e1eafaacb6c92f2a04171/68747470733a2f2f73332d61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f6730762d6861636b6d642d696d616765732f75706c6f6164732f75706c6f61645f62313061303365653566333061623433323163323437386161316634663735342e706e67>
github 01:57:25
As discussed in <https://g0v.hackmd.io/@mrorz/BksBf58KI#%E9%80%81%E5%87%BA-source-%E8%88%87-reason-%E5%BE%8C%E7%9A%84%E8%A8%8A%E6%81%AF%E9%87%8D%E8%A4%87%E5%95%8F%E9%A1%8C|https://g0v.hackmd.io/@mrorz/BksBf58KI#%E9%80%81%E5%87%BA-source-%E8%88%87-reason-%E5%BE%8C%E7%9A%84%E8%A8%8A%E6%81%AF%E9%87%8D%E8%A4%87%E5%95%8F%E9%A1%8C> This PR turns the 2 bubbles after LIFF response into carousel to make it more compact, hopefully the message will not overwhelm the user. <https://user-images.githubusercontent.com/108608/81098815-790e2000-8f3c-11ea-8c1d-d2917421ab65.png|image> Note that • In response to source (first carousel), both bubble are all call-to-actions (we want user to both provide more info and share) • In response to reason (2nd carousel), not many people updates their own reason, thus use gray color instead; also, the update bubble is moved to the right (2nd bubble).
github 01:59:45
*Pull Request Test Coverage Report for <https://coveralls.io/builds/30576473|Build 771>* • *2* of *2* *(100.0%)* changed or added relevant lines in *2* files are covered. • No unchanged relevant lines lost coverage. • Overall coverage increased (+*0.02%*) to *98.652%* * * * * * * *:yellow_heart: - <https://coveralls.io|Coveralls>*
github 09:23:09
Draft 也會抓出來啊 😦
ready to review 會再一次 XDD
nonumpa 09:33:34
Draft 也會抓出來啊 😦
github 11:11:40
this seems like the old behavior of original commit, but I rebase the commit, it should look like these now: <https://user-images.githubusercontent.com/12843409/81135340-5276d600-8f8a-11ea-806c-b92fbfde2c68.png|截圖 2020-05-06 上午11 10 29> <https://user-images.githubusercontent.com/12843409/81135343-560a5d00-8f8a-11ea-873a-99dba391476d.png|截圖 2020-05-06 上午11 10 14>
mrorz 11:16:54
ready to review 會再一次 XDD
github 11:20:18
It seems that it only happens on specific articles that has only 1 line, and the line is super short, on desktop. You can search for `555` on <https://cofacts.hacktabl.org/replies|staging> to see the following sample <https://user-images.githubusercontent.com/108608/81135607-72f36000-8f8b-11ea-975b-7a81433e250a.png|image>
@mrorz 因為我有 rebase 然後改掉原本的 commit
所以可能跟之前 deploy 到 staging 得不太一樣
可能要再 deploy 一次試試
目前我這看起來是好的(如 comment 的附圖)
感謝,確認正常
yanglin 11:52:28
@mrorz 因為我有 rebase 然後改掉原本的 commit
所以可能跟之前 deploy 到 staging 得不太一樣
可能要再 deploy 一次試試
目前我這看起來是好的(如 comment 的附圖)
mrorz 12:22:22
Replied to a thread: 2020-05-06 00:16:01
請問 @yanglin5689446 今天晚上有空線上會議嗎~

今天晚上 @stbb1025 會與 @lucien 討論 UI (detail page mobile 版),也想問問你有沒有什麼想當面問的

時間大概會是 @lucien 到場的時間(8pm~11pm 之間的不知道什麼時間)
yanglin 12:35:28
可以喔
  • 1
  • 👍1
mrorz 12:36:40
感謝,確認正常
github 12:37:27
Confirmed that its' fixed after rebase.
mrorz 12:59:25
Replied to a thread: 2020-05-04 01:13:01
這個變更太多了,為了找出有沒有問題
我先把它放到 staging 上跑跑看囉

如果大家最近打 staging API 出問題請提出,說不定是這個 PR 的問題
  • 👍2
github 14:14:23
New article details page UI <https://www.figma.com/file/zpD45j8nqDB2XfA6m2QskO/Cofacts-website?node-id=681%3A0|mockup>
github 16:25:12
Change pagination to infinity load
ggm 17:33:34
@darkbtf 留意個
lucien 19:00:55
今天我可以線上會議嗎?
github 19:21:33
But `null` and `undefined` are different in mongodb. For example ``` db.collection('inventory').find({ item: null }); ``` ``` db.collection('inventory').find({ item: { $exists: false } }); ``` I'm not sure there is any effect in the future, so I choose the precise defines.
mrorz 19:33:57
lucien 19:55:52
我大概九點
stbb1025 21:13:41
開始叫我唷
lucien 21:13:57
我正在使用 tico
lucien 21:14:03
毫無反應
mrorz 21:15:16
@lucien @stbb1025 我們是不是換個 Jitsi 才有聲音呀
這裡都白白看不到你們
stbb1025 21:15:35
我跟@lucien 可以通話
lucien 21:16:24
我跟 nick 一國
stbb1025 21:16:33
yeah
mrorz 21:18:51
@yanglin5689446 我們在這裡開會
https://tico.chat/powercall?room=cofactshack&type=timeFirst
tico.chat
The considerate messenger. Chat with whom you care about always at the best moments and times
stbb1025 21:19:37
@lucien 你不見了
lucien 21:19:53
我聽得到各位
lucien 21:20:03
你們聽不到我
mrorz 21:21:24
@yanglin5689446 @lucien @stbb1025 https://meet.jit.si/cofactshack
meet.jit.si
Join a WebRTC video conference powered by the Jitsi Videobridge
mrorz 21:53:36
Stack Overflow
I can only find questions where people have the opposite problem. I want my fixed content to go above the iOS keyboard. Image of the problem: I want iOS to behave like Android. Is there a simpl...
mrorz 21:54:45
Pixel 2: Top 與 Bottom 在鍵盤出現時都會出現
iPhone X: 整個螢幕往上推,top 會被推不見,bottom 仍然黏著
lucien 21:55:34
Screenshot_20200506-215518.png
mrorz 22:02:08
@yanglin5689446 FYI: 從我們的 analytics 做的手機上的 browser version 統計
https://g0v.hackmd.io/@mrorz/rksKX45D8#LIFF-compatibility
HackMD
20200408 會議紀錄 ===== &gt; Workis: MrOrz, bil, lucien &gt; 線上:志超 &gt; 線上開會:<https://tico.chat/powercall?room=>
github 22:11:06
Hi all, I really enjoy your works with cofacts chatbot. I can easily distinguish the truth of messages from my parents, that is reeeally good. However, there is a feature that I need is not in the current version. The feature I need is `sharing the results to other chat rooms`, such as my parent's private message or family channel etc. Thanks for your effort. You are the best.
mrorz 22:17:43
material-ui.com
Tabs make it easy to explore and switch between different views.
mrorz 22:45:02
Material design: dialog with options
image.png
mrorz 22:45:28
Option with description
image.png
mrorz 22:56:42
開新視窗的 icon
image.png
mrorz 23:13:01
material-ui.com
Menus display a list of choices on temporary surfaces.
2020-05-07
mrorz 00:06:59
LINE bot 的新流程在 staging 囉
大家可以打開手機到 staging 玩玩看
staging 這邊請:https://lin.ee/1QUzEX4nI

跟上週不太一樣的地方有:
1. 阻擋桌面版點出 LIFF
2. 修改理由與分享 flex message 的排列
3. 按下「有用」/「沒用」後的 LIFF 告知使用者已經紀錄 voting
PR 堆得很高 :books: 我想如果 PR 放一個月都沒人看的話,就要包裹通過囉 .__.
https://github.com/cofacts/rumors-line-bot/pulls
github 00:10:31
Yeah I know about the `$exists`. But if you declare a field to be any type that is not null, say, a string, can you still assign `null` to that field?
github 00:54:16
Hi <https://github.com/shrimp509|@shrimp509> it's glad to see the chatbot helps :) If you feel like sharing the results from Cofacts to other chatrooms, you may either 1. long-press the reply and choose "share". After that you can select multiple messages to share (usually the reason and the references provided by the editor) and then choose the target chatrooms. 2. When the chatbot asks if the reply is useful, reply "yes". The chatbot will then encourage you to share this reply with friends. Option 2 works like this: <https://user-images.githubusercontent.com/108608/81205116-d23d8880-8ffc-11ea-9a15-fb2cd86e87b8.png|Screenshot_20200507-004353_2> <https://user-images.githubusercontent.com/108608/81205127-d5387900-8ffc-11ea-819c-a0b28bb97ef4.png|Screenshot_20200507-004411> We designed this flow because we really needs the user's feedback on usefulness of a reply, and we don't want to ask user to share a reply if they think it's not useful in the first place.
github 09:06:56
delightfullychaotic 2020-05-07 09:11:40
這是昨天台南小松的熱情朋友,哈哈,我自己都不知道分享給朋友可以純文字。
github 09:06:57
Got it!! I didn't notice the share button after choosing one of the results. Thanks for your detail explanation of why you design like this. I understand your consideration. Thank you again.
delightfullychaotic 09:11:40
這是昨天台南小松的熱情朋友,哈哈,我自己都不知道分享給朋友可以純文字。
  • 👍1
github 11:54:33
If `process.env.IMAGE_MESSAGE_ENABLED` is not true, image messages will not be tracked. Maybe we can extract this along with <https://github.com/cofacts/rumors-line-bot/blob/7a93a085e75e9d3ac1bcd8018b4a1625e4353bdf/src/webhook/index.js#L168|index.js L168>, <https://github.com/cofacts/rumors-line-bot/blob/7a93a085e75e9d3ac1bcd8018b4a1625e4353bdf/src/webhook/index.js#L179|index.js L179> to the beginning of <https://github.com/cofacts/rumors-line-bot/blob/7a93a085e75e9d3ac1bcd8018b4a1625e4353bdf/src/webhook/index.js#L83|process message>.
mrorz 12:22:22
@ggm 關於這個呀 https://github.com/cofacts/rumors-line-bot/pull/182/files/d03bb7533c63264eb241b4cee08548df6fa050df#discussion_r418464972

我有看到你後來把 _id 拿掉,_不過我當時想討論的其實是為什麼要放 `_id`, `userId`, `articleId` 等等欄位進 model,但沒看到有 code 在存取這些欄位。
我已經滿久沒有在用類似這樣的 ORM / Active record pattern 的介面了,通常就是 get 一個物件,呼叫 update 或 insert 也都是直接呼叫 client 傳一個物件進去,沒有在 `obj.save()` 這樣。
_id 是 mongodb 預設會有的欄位,在top level schema我記得是拿不掉的
我在說的是像這個檔案
src/database/models/userArticleLink.js

我可以理解用 static method 來把此種 model 相關的 DB operation 收納在一個 class 裡面,但我不知道為什麼會有 L70 開始的下面那些 field,因為目前好像沒有人會去存取這些 field。

他看起來像是預留給 ORM 機制,
也就是之後會有人從資料庫讀出來之後灌進 `UserArticleLink` instance 用的,或有人未來會 `new UserArticleLink` 然後填資料進去。
還是 GGM 寫下面那些 field,其實是為了 typing? 像這樣
噢我原本要解釋打到一半睡著 XD
拿掉 `_id` 是因為整個過程中我們都不會需要 `_id` ,所以不需要對 `_id` 做 verify
然後另外那些變數是 class public field declarations,現階段了作用是為了給開發者看說這個 class 會有這個欄位,以及要給 jsdoc 看有這個定義
跟 ORM 其實比較無關,是為了 typing,當我呼叫 `findOrInsertByUserId` 取得實體之後我會有這個 `newReplyNotifyToken` property
了解,所以是 for type definition
不透過 class 做的話,另外寫 jsdoc 要另外
```@typedef {Object} UserSettings```
然後再寫一些
```@property```
這樣
對對
但這邊只是給 jsdoc 對吧
我自己之前接觸過的 repo 其實也大多是以另外寫 type 為主,API function 呼叫時,再去使用那些 type。

利用 class field type definition 來把 typing 與 API 結構放在一起是第一次看到
然後存取的部分應該會發生在後面,譬如拿 `newReplyNotifyToken` 出來用,或判斷時間要送推播的時候
用 typescript 當然就可以把 type 抽出來呀
但「把 type 抽出來」這種 pattern,在 JSDoc 裡相對應的語法,就是你不想用的 `@typedef`
我以為你用 class 是想說之後如果轉 typescript 也直接用 typescript class 無痛(?)轉
對啊這也是
雖然 typescript 其實會抱怨說這些 field 沒有 initializer,不過那也是之後轉 typescript 再來研究就好的事 (茶
我覺得這樣寫法蠻奇怪的
看起來應該要有 factory 去產你要的結構
我覺得 class field type definition 會比寫 jsdoc 還具體還好用,在 typescript 也用得到,到時候就把 `@type {boolean}` 這個換掉就好
現在兩個放在一起,就會有這樣的問題
為什麼會像是要用 factory 生成呀?
GGM 484 覺得這些寫 JS 的人都很喜歡 factory pattern
像這個討論串:https://g0v-tw.slack.com/archives/C2PPMRQGP/p1588333070310900
我覺得沒那麼複雜啦 XD
像這個簡單的例子,就是一個 Rectangle 裡面有 height 和 width 這樣而已
我覺得有點像是習慣差異耶

從 C++ / Java 來的人很直覺會想說 type definition 放在 class 很清楚

但大家 Javascript 導入 Typescript 的過程與 mind model,開發者心裡想的其實是,typing 是一種幫我檢查資料形狀的「外掛」
所以 JS developer 會直覺的使用 JS 常用的手段,例如 export object literal 出去當 API、用 thunk 解決呼叫時間問題等等,然後再用 type 去 “annotate” 這些 expose 出去的 API
最主要的目的就是想要透過 class 來描述我從 database (mongo) 拿出來有哪些欄位
另外一個比較意識形態的問題是,Javascript 社群有拒C文化 (這裏的 C 是 class),比較擁抱 fp⋯⋯ XD
es6 出了 class,越來越 js 專案轉 ts,typescript 也有完整的 class,我覺得抗拒 C 文化倒是很難說⋯
其實不用 class 也是寫一堆
```Object.defineProperty```
```A.prototype.oooxxx```
之類的東西呀其實
Typescript 我覺得很 class
真怪怎麼沒有變色 XD
尤其開始用reflect metadata之後
嗯嗯
我只是想解釋為什麼我跟 Lucien 會針對 code 裡用到 class 的不同的地方,都下意識地提出說可以用 factory 解的原因

因為學校裡有教的確實是比較偏 OOP 的習慣,所以我也很好奇我什麼時候 pickup 了這種 JS 的思維
把工廠跟產出物放在同一個class會導致你沒辦法在 constructor init 產出物的property
你進typescript之後會很難處理
Mongo orm 會用 model 跟 document 分別描述這兩個東西
工廠其實是 static function (method) 為什麼會影響 constructor init ?
怎麼描述的做法我覺得都有,像是 `loopback 3` 是用分開描述有點像我現在寫的這樣,有一個 .json 另一個 .js,`loopback 4` 則是寫在一個檔案描述,不過他就導入 typescript 了
然後 `sailsjs` 就是把工廠和產出物寫在一起,譬如 `People.create()` 和 `people.save()` 之類的,透過 static function 切開
其實我覺得 `UserArticleLink` 這個 class 好像並不是設計給人 `new` 的,之後有 private constructor 的時候可能一樣要擋起來,跟 `CofactsMongoClient` 一樣
mrorz 12:23:42
我已經滿久沒有在用類似這樣的 ORM / Active record pattern 的介面了,通常就是 get 一個物件,呼叫 update 或 insert 也都是直接呼叫 client 傳一個物件進去,沒有在 `obj.save()` 這樣。
lucien 12:30:46
_id 是 mongodb 預設會有的欄位,在top level schema我記得是拿不掉的
mrorz 12:36:08
我在說的是像這個檔案
src/database/models/userArticleLink.js

我可以理解用 static method 來把此種 model 相關的 DB operation 收納在一個 class 裡面,但我不知道為什麼會有 L70 開始的下面那些 field,因為目前好像沒有人會去存取這些 field。

他看起來像是預留給 ORM 機制,
也就是之後會有人從資料庫讀出來之後灌進 `UserArticleLink` instance 用的,或有人未來會 `new UserArticleLink` 然後填資料進去。
mrorz 12:46:59
還是 GGM 寫下面那些 field,其實是為了 typing? 像這樣
image.png
ggm 12:47:00
噢我原本要解釋打到一半睡著 XD
ggm 12:47:57
拿掉 `_id` 是因為整個過程中我們都不會需要 `_id` ,所以不需要對 `_id` 做 verify
  • 👍1
ggm 12:49:55
然後另外那些變數是 class public field declarations,現階段了作用是為了給開發者看說這個 class 會有這個欄位,以及要給 jsdoc 看有這個定義
  • 👌1
ggm 12:51:55
跟 ORM 其實比較無關,是為了 typing,當我呼叫 `findOrInsertByUserId` 取得實體之後我會有這個 `newReplyNotifyToken` property
mrorz 12:52:53
了解,所以是 for type definition
ggm 12:52:58
不透過 class 做的話,另外寫 jsdoc 要另外
```@typedef {Object} UserSettings```
ggm 12:53:32
然後再寫一些
```@property```
這樣
mrorz 12:53:38
對對
lucien 12:56:09
但這邊只是給 jsdoc 對吧
mrorz 12:59:45
我自己之前接觸過的 repo 其實也大多是以另外寫 type 為主,API function 呼叫時,再去使用那些 type。

利用 class field type definition 來把 typing 與 API 結構放在一起是第一次看到
ggm 13:00:52
然後存取的部分應該會發生在後面,譬如拿 `newReplyNotifyToken` 出來用,或判斷時間要送推播的時候
ggm 13:02:06
用 typescript 當然就可以把 type 抽出來呀
mrorz 13:04:05
但「把 type 抽出來」這種 pattern,在 JSDoc 裡相對應的語法,就是你不想用的 `@typedef`
mrorz 13:06:51
我以為你用 class 是想說之後如果轉 typescript 也直接用 typescript class 無痛(?)轉
ggm 13:06:58
對啊這也是
mrorz 13:08:02
雖然 typescript 其實會抱怨說這些 field 沒有 initializer,不過那也是之後轉 typescript 再來研究就好的事 (茶
lucien 13:10:06
我覺得這樣寫法蠻奇怪的
lucien 13:10:37
看起來應該要有 factory 去產你要的結構
ggm 13:10:53
我覺得 class field type definition 會比寫 jsdoc 還具體還好用,在 typescript 也用得到,到時候就把 `@type {boolean}` 這個換掉就好
lucien 13:10:57
現在兩個放在一起,就會有這樣的問題
ggm 13:11:39
為什麼會像是要用 factory 生成呀?
mrorz 13:12:09
GGM 484 覺得這些寫 JS 的人都很喜歡 factory pattern
像這個討論串:https://g0v-tw.slack.com/archives/C2PPMRQGP/p1588333070310900
Johnson Liang
@ggm PR review 囉,請過目
https://github.com/cofacts/rumors-line-bot/pull/182

mongoClient 的實作我覺得有些太複雜了,abstraction 也有點怪 QQ
ggm 13:12:43
我覺得沒那麼複雜啦 XD
ggm 13:13:08
MDN Web Docs
JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.
ggm 13:14:04
像這個簡單的例子,就是一個 Rectangle 裡面有 height 和 width 這樣而已
mrorz 13:15:05
我覺得有點像是習慣差異耶

從 C++ / Java 來的人很直覺會想說 type definition 放在 class 很清楚

但大家 Javascript 導入 Typescript 的過程與 mind model,開發者心裡想的其實是,typing 是一種幫我檢查資料形狀的「外掛」
mrorz 13:16:38
所以 JS developer 會直覺的使用 JS 常用的手段,例如 export object literal 出去當 API、用 thunk 解決呼叫時間問題等等,然後再用 type 去 “annotate” 這些 expose 出去的 API
ggm 13:17:15
最主要的目的就是想要透過 class 來描述我從 database (mongo) 拿出來有哪些欄位
mrorz 13:18:27
另外一個比較意識形態的問題是,Javascript 社群有拒C文化 (這裏的 C 是 class),比較擁抱 fp⋯⋯ XD
ggm 13:21:21
es6 出了 class,越來越 js 專案轉 ts,typescript 也有完整的 class,我覺得抗拒 C 文化倒是很難說⋯
ggm 13:23:35
其實不用 class 也是寫一堆
```Object.defineProperty```
```A.prototype.oooxxx```
之類的東西呀其實
lucien 13:23:36
Typescript 我覺得很 class
ggm 13:23:53
真怪怎麼沒有變色 XD
lucien 13:23:58
尤其開始用reflect metadata之後
mrorz 13:32:13
嗯嗯
我只是想解釋為什麼我跟 Lucien 會針對 code 裡用到 class 的不同的地方,都下意識地提出說可以用 factory 解的原因

因為學校裡有教的確實是比較偏 OOP 的習慣,所以我也很好奇我什麼時候 pickup 了這種 JS 的思維
  • 👍1
lucien 13:56:08
把工廠跟產出物放在同一個class會導致你沒辦法在 constructor init 產出物的property
lucien 13:56:35
你進typescript之後會很難處理
lucien 13:57:10
Mongo orm 會用 model 跟 document 分別描述這兩個東西
ggm 14:16:17
工廠其實是 static function (method) 為什麼會影響 constructor init ?
ggm 14:21:31
怎麼描述的做法我覺得都有,像是 `loopback 3` 是用分開描述有點像我現在寫的這樣,有一個 .json 另一個 .js,`loopback 4` 則是寫在一個檔案描述,不過他就導入 typescript 了
ggm 14:22:58
然後 `sailsjs` 就是把工廠和產出物寫在一起,譬如 `People.create()` 和 `people.save()` 之類的,透過 static function 切開
mrorz 14:23:43
其實我覺得 `UserArticleLink` 這個 class 好像並不是設計給人 `new` 的,之後有 private constructor 的時候可能一樣要擋起來,跟 `CofactsMongoClient` 一樣
  • 👍1
github 14:57:47
Yes, if using mongodb ``` $&gt; db.ppl.insert({phoneNumber: null}) WriteResult({ "nInserted" : 1 }) $&gt; db.ppl.insert({phoneNumber: '0912'}) WriteResult({ "nInserted" : 1 }) $&gt; db.ppl.find() { "_id" : ObjectId("5eb3ad6a9a5e1f8e59885998"), "phoneNumber" : null } { "_id" : ObjectId("5eb3ad739a5e1f8e59885999"), "phoneNumber" : "0912" } ``` There are 2 options: 1. [require=true] Should be exist, then string or null 2. [require=false] It would be string or undefined (not exists) But I think yours is better to be less verbose first, and then update if need.
github 15:02:23
btw, If we follow option 1, then we can query easily ``` db.collection('inventory').find({ itemA: null, itemB: 'book' }); ``` Not sure if it's important for now
github 15:10:55
Hmm if I want to find a document that has one field that does not exist, the first idea that comes to my mind would be `{itemA: {$exists: false}}`, did not think of testing `null` in the first place. The syntax also mixes well with other conditions in place (i.e. `{itemA: {$exists: false}, itemB: 'book'}`). Slightly longer than the `null` version, but I think its occurrence in codebase should be less than field definitions.
github 15:15:29
Seems that json schema we are currently using _does_ block non-required fields from being set to `null`. <https://user-images.githubusercontent.com/108608/81265339-824ed800-9075-11ea-8365-a52860f545f6.png|image>
github 15:21:44
On the other hand, one draw back (?) of using non-required fields would be that it's a bit counterintuitive when we want to set a field conditionally, for instance, ``` const person = { name: 'foo', ...(revealGender ? {gender: input} : {}) } ``` Setting everything to required + allow null for some fields forces us to specify all fields, but is easier (?) to set conditional content: ``` const person = { name: 'foo', gender: revealGender ? input : null, bloodType: null, weight: null, height: null, birthday: null } ```
github 15:31:17
&gt; Seems that json schema we are currently using _does_ block non-required fields from being set to `null`. You are not using the current one ? Should be use `oneOf` in your demo case ?
github 15:44:37
&gt; Hmm if I want to find a document that has one field that does not exist, the first idea that comes to my mind would be `{itemA: {$exists: false}}`, did not think of testing `null` in the first place. I'm not sure that is the first idea. There are different usages in different situations. Let's talk about `newReplyNotifyToken` if it's expired or not working or something. I want to set to be `null`, not `undefined`, and find those documents by using `null` not `{$exists: false}`
github 15:45:55
In the demo case I am testing if jsonschema can block `null` when it's assigned to non-required fields. Personally I think we should either stick to (a) field not-required + can't be null, or (b) all-field-required + can be null, not a mixture of (a) and (b). While the current code is in (b), in the demonstration I was trying to show that (a) also works as exptected.
github 19:11:55
Interesting aspect of `newReplyNotifyToken`, didn't think of that. When `newReplyNotifyToken` is expired, I would ask the user if they want to delete the token. If they say yes, we invoke `db.collection('userArticleLinks').update({$unset: {newReplyNotifyToken: ''}})`. That's indeed longer than setting `newReplyNotifyToken` to `null`.
2020-05-08
mrorz 00:59:19
因為最近會跟其他有興趣實作 Cofacts 的組織接觸,所以更新了一下 developer homepage:
https://hackmd.io/@mrorz/r1nfwTrgM

裡面有
• 更新的系統架構圖
• 更新的 DB index 圖 https://g0v.hackmd.io/@mrorz/S1caurZq8
• 新畫的 ER Model 圖https://g0v.hackmd.io/@mrorz/S1caurZq8
希望這樣對新開發者有助益~
HackMD
【Cofacts 真的假的】Homepage for Developers ====== ## Source codes Cofacts is comprised of several com
HackMD
Updated at 2020/5/7
  • 👍1
mrorz 01:29:22
另外,因為今天 @darkbtf 問到關於 userId / appId 的問題,我覺得應該要把 Cofacts 系統的 userId / appId 機制給寫下來:

https://g0v.hackmd.io/ZcoUOX_-RQSkJyl5xz4_Zg

文件還沒寫完,之後會需要討論說,未來要擴充的話要怎麼處理的細節。
  • 👍1
yanglin 12:29:21
@mrorz 視覺化搜尋趨勢那邊可能需要資料 api
mrorz 12:29:39
對對那個要補
  • 👌1
szeming 13:35:45
@szeming has joined the channel
yanglin 18:03:25
今天稍微調查了一下發現
只要加了 `requestedForReply`
Hyperlink 的 polling 就會沒有 response
導致 app 一直轉圈圈
yu_tainan 18:20:20
@ytyubox has joined the channel
mrorz 18:38:49
怪了,為什麼會跟 hyperlink 有關 QQ
yanglin 18:40:36
我也不太確定 我之後再試試看
我現在是在 Article details 的頁面的 GetArticle query 新增 就會
2020-05-09
nonumpa 00:15:01
https://g0v.hackmd.io/eIeU2g86Tfu5VnLazNfUvQ#資料表:UserSettings
問個問題,我們會讓使用者同時使用 Line Notify 和 Push Api 嗎
還是會直接從 server 端就決定要用哪種?
兩個都開好像沒什麼意義,功能一樣(雖然 Line notify 可以選擇傳到別的聊天室,但通知訊息主要是針對使用者查過的訊息,預期使用者不會這樣用。)
Hmm 做成 server 端(用 env vars)決定好像比較簡單齁
我們可能英文版與 staging 用 LINE notify (畢竟也沒付費帳號)
localhost 與 production 用 Push API
nonumpa 00:35:24
第二個問題,Line notify 我想要做成打開通知就 link,關掉通知就 revoke token,這樣做比較簡單
不然我們除了開關通知可能還要另外做一個 link/revoke 的功能
但其實點了 revoke 就等於關閉通知,點了開啟通知就等於要 link
(關閉通知可以不用 revoke,link 可以不用開啟通知)
好呀就這樣辦~
mrorz 03:43:01
Hmm 做成 server 端(用 env vars)決定好像比較簡單齁
mrorz 03:43:47
我們可能英文版與 staging 用 LINE notify (畢竟也沒付費帳號)
localhost 與 production 用 Push API
  • 1
  • 1
mrorz 03:53:00
好呀就這樣辦~
hoyang 08:42:01
@hoyangtsai has joined the channel
github 14:03:11
*Pull Request Test Coverage Report for <https://coveralls.io/builds/30669027|Build 787>* • *59* of *62* *(95.16%)* changed or added relevant lines in *5* files are covered. • No unchanged relevant lines lost coverage. • Overall coverage decreased (*-0.5%*) to *96.177%* * * * * * * *:yellow_heart: - <https://coveralls.io|Coveralls>*
ggm 15:04:16
我 rebase dev 之後,跑 test 會噴這個
``` PASS src/lib/__tests__/gql.js
● Console

console.error src/lib/gql.js:53
GraphQL operation contains error: [ { message: 'Runtime error' } ]```
Pass 的話不用理~
那個是在測 error handling 嗎?
ggm 15:04:22
要理他嗎?
github 16:14:09
The vertical spacing is imbalanced <https://user-images.githubusercontent.com/108608/81465180-95d77b80-91fa-11ea-8dd5-63061579f71f.png|image> I suggest adding negative bottom margin to `ul.articleList` to undo the bottom margin of `ArticleItem`. Of course other solutions also apply (such as removing the bottom margin for last `ArticleItem` using `:last-child`), but we should still manage the margin of `ul.articleList`; it is still having the default margin defined by the browser. <https://user-images.githubusercontent.com/108608/81465177-87895f80-91fa-11ea-8af7-664d4a0d4b08.png|image>
github 16:14:09
Instead of using `refetch` that cleans up `listArticlesData`, please consider leveraging `fetchMore` which allows us to describe how new data is appended to `listArticlesData` using `updateQuery()`. In this way we don't need to maintain a separate state `articleEdges`. Reference: <https://www.apollographql.com/docs/react/data/pagination/|https://www.apollographql.com/docs/react/data/pagination/> Introductory post: <https://www.apollographql.com/blog/pagination-and-infinite-scrolling-in-apollo-client-59ff064aac61|https://www.apollographql.com/blog/pagination-and-infinite-scrolling-in-apollo-client-59ff064aac61> Also, we don't need to store `nextCursor` in separate state; we can just get the cursor from the last edge in the data. Lastly, the current implementation does not preserve the page if the user enters a detail page from the list and hit back to return. We hope that using `fetchMore` could solve the current issue, since apollo-cache should reserve the items loaded so far and next-router should manage the scroll position.
github 16:14:09
If we leverage `loadMore` and get cursor from last edge directly in `ArticlePageLayout`, we can leave the pagination logic entirely in where pagination happens, reduce the props passed to `LoadMore`. Personally I think `onClick` and `loading` should be enough; we can even detect if we reached the last page outside of `&lt;LoadMore&gt;`.
github 16:14:09
It's a good try of changing the pagination into infinite load. I think we can • leverage `loadMore` instead of `refetch` and see if it helps with preserving pages when user navigates from detail page back to list page. • reduce the props passed to `LoadMore` as we encapsulate pagination logic in the component that uses `LoadMore`.
github 23:29:50
LGTM! Thanks for your patience and the explanation, very inspiring. It seems that it is possible to further simplify the logic of `MongoClient.getInstance()`, see the comment below.
@ggm 有 conflict 唷

也可以參考一下我的 Comment ~
真怪我之前有 rebase 了說,我又 rebase 然後 push 囉
github 23:29:50
Seems that this is equivalent to: ``` // It returns promise anyway, no need for async in this function. // If we want to hint that this returns a promise, just use JSDoc `@returns {Promise&lt;CofactsMongoClient&gt;}` static getInstance() { // combines set-by-default and default setting return this._instance = this._instance || (async () =&gt; { const client = new CofactsMongoClient(MONGODB_URI); await client.mongoClient.connect(); return client; })(); } ``` Giving the async arrow function a good name can further improve its readability: ``` // Somewhere outside the class: // async function instantiateClientAndConnect() { const client = new CofactsMongoClient(MONGODB_URI); await client.mongoClient.connect(); return client; } // Then, back to inside the class, the code is super easy to understand now: // static getInstance() { return this._instance = this._instance || instantiateClientAndConnect(); } ```
@ggm 有 conflict 唷

也可以參考一下我的 Comment ~
真怪我之前有 rebase 了說,我又 rebase 然後 push 囉
2020-05-10
github 01:32:17
Fix this error: old version of articleCategories is null, instead of being initialized to empty list. ``` { "errors": [ { "message": "illegal_argument_exception", "locations": [ { "line": 2, "column": 3 } ], "path": [ "CreateArticleCategory" ], "extensions": { "code": "INTERNAL_SERVER_ERROR", "exception": { "name": "ResponseError", "meta": { "body": { "error": { "root_cause": [ { "type": "remote_transport_exception", "reason": "[VxKgRte][127.0.0.1:9300][indices:data/write/update[s]]" } ], "type": "illegal_argument_exception", "reason": "failed to execute script", "caused_by": { "type": "script_exception", "reason": "runtime error", "script_stack": [ "found = ctx._source.articleCategories.stream()\n .filter(", " ^---- HERE" ], "script": "\n def found = ctx._source.articleCategories.stream()\n .filter(ar -&gt; ar.get('categoryId').equals(params.articleCategory.get('categoryId')))\n .findFirst();\n\n if (found.isPresent()) {\n HashMap ar = found.get();\n if (ar.get('status').equals('DELETED')) {\n ar.put('status', 'NORMAL');\n ar.put('userId', 'model_test');\n ar.put('appId', 'DEVELOPMENT_BACKEND');\n ar.put('updatedAt', '2020-05-07T11:56:48.759Z');\n } else {\n ctx.op = 'none';\n }\n } else {\n ctx._source.articleCategories.add(params.articleCategory);\n ctx._source.normalArticleCategoryCount = ctx._source.articleCategories.stream().filter(\n ar -&gt; ar.get('status').equals('NORMAL')\n ).count();\n }\n ", "lang": "painless", "caused_by": { "type": "null_pointer_exception", "reason": null } } }, "status": 400 }, "statusCode": 400, "headers": { "content-type": "application/json; charset=UTF-8", "content-length": "1401" }, "warnings": null, "meta": { "context": null, "request": { "params": { "method": "POST", "path": "/articles/doc/zzyt710qo56u/_update", "body": "{\"script\":{\"source\":\"\\n def found = ctx._source.articleCategories.stream()\\n .filter(ar -&gt; ar.get('categoryId').equals(params.articleCategory.get('categoryId')))\\n .findFirst();\\n\\n if (found.isPresent()) {\\n HashMap ar = found.get();\\n if (ar.get('status').equals('DELETED')) {\\n ar.put('status', 'NORMAL');\\n ar.put('userId', 'model_test');\\n ar.put('appId', 'DEVELOPMENT_BACKEND');\\n ar.put('updatedAt', '2020-05-07T11:56:48.759Z');\\n } else {\\n ctx.op = 'none';\\n }\\n } else {\\n ctx._source.articleCategories.add(params.articleCategory);\\n ctx._source.normalArticleCategoryCount = ctx._source.articleCategories.stream().filter(\\n ar -&gt; ar.get('status').equals('NORMAL')\\n ).count();\\n }\\n \",\"lang\":\"painless\",\"params\":{\"articleCategory\":{\"userId\":\"model_test\",\"appId\":\"DEVELOPMENT_BACKEND\",\"aiModel\":\"bert\",\"aiConfidence\":1,\"positiveFeedbackCount\":0,\"negativeFeedbackCount\":0,\"categoryId\":\"mD2n7nEBrIRcahlYLAr7\",\"status\":\"NORMAL\",\"createdAt\":\"2020-05-07T11:56:48.759Z\",\"updatedAt\":\"2020-05-07T11:56:48.759Z\"}}},\"_source\":[\"articleCategories.*\"]}", "querystring": "", "headers": { "User-Agent": "elasticsearch-js/6.8.6 (linux 4.4.0-137-generic-x64; Node.js v12.14.1)", "Content-Type": "application/json", "Content-Length": "1269" }, "timeout": 30000 }, "options": { "warnings": null }, "id": 89085 }, "name": "elasticsearch-js", "connection": { "url": "<http://cofacts-db-staging:9200/>", "id": "<http://cofacts-db-staging:9200/>", "headers": {}, "deadCount": 0, "resurrectTimeout": 0, "_openRequests": 0, "status": "alive", "roles": { "master": true, "data": true, "ingest": true, "ml": false } }, "attempts": 0, "aborted": false } } } }, "authError": false } ], "data": { "CreateArticleCategory": null } } which can be reproduced by this mutation: ``` mutation { CreateArticleCategory( articleId: "zzyt710qo56u" categoryId: "mD2n7nEBrIRcahlYLAr7" aiModel: "bert" aiConfidence: 1.0 ) { articleId categoryId } }
liao.jason2 11:11:36
@liao.jason2 has joined the channel
liao.jason2 11:11:36
@liao.jason2 has joined the channel
github 15:47:07
LGTM! It works on staging, I think we can <https://github.githubassets.com/images/icons/emoji/shipit.png|:shipit:>
github 16:37:11
Please `npm run lint:fix` first. <https://travis-ci.org/github/cofacts/rumors-site/builds/684561774|https://travis-ci.org/github/cofacts/rumors-site/builds/684561774>
github 16:38:13
Suggest adding `copyBtnRef.current`, `content` and `onClick` to dependency array • Adding `copyBtnRef.current` is for the case when DOM of the the button is reconstructed, the `ClipboardJS` instance should be bound to the new DOM element instead. • Adding `content` and `onClick` is to prevent the case when these props are updated, ClipboardJS is still connecting to old callbacks that access an old JS closure.
github 16:55:57
Deploying to production • Backup: `gcs/2020-05-10-before-migration` • Migrated at: TBD *1st round* ``` 16:59 ≎ ~/workspace/rumors-db ➠ npx babel-node db/migrations/202005-add-article-categories.js ResponseError: Response Error at IncomingMessage.&lt;anonymous&gt; (/Users/mrorz/workspace/rumors-db/node_modules/@elastic/elasticsearch/lib/Transport.js:296:25) at IncomingMessage.emit (events.js:205:15) at IncomingMessage.EventEmitter.emit (domain.js:471:20) at endReadableNT (_stream_readable.js:1137:12) at processTicksAndRejections (internal/process/task_queues.js:84:9) { name: 'ResponseError', meta: { body: { took: 4641, timed_out: false, total: 36068, updated: 3577, deleted: 0, batches: 4, version_conflicts: 423, noops: 0, retries: [Object], throttled_millis: 0, requests_per_second: -1, throttled_until_millis: 0, failures: [Array] }, statusCode: 409, headers: { 'content-type': 'application/json; charset=UTF-8', 'content-length': '133674' }, warnings: null, meta: { context: null, request: [Object], name: 'elasticsearch-js', connection: [Object], attempts: 1, aborted: false } } } ```
github 17:35:55
This PR: • tells elasticsearch to do nothing when `articleCategories` exists • log everything when error occurs • log response when success
@darkbtf 麻煩看一下
我小改了一下 migration
改完之後想說在 production 再重跑一次
沒有指定 op = ‘noop’ 的話是不是會無差別寫入呀
第一次完全沒 `noop` ,但最新的 Articles 應該都要有 articleCategories
mrorz 17:37:27
@darkbtf 麻煩看一下
我小改了一下 migration
改完之後想說在 production 再重跑一次
mrorz 18:22:51
Deploying to production • Backup: `gcs/2020-05-10-before-migration` • Migrated at: TBD *1st round* ``` 16:59 ≎ ~/workspace/rumors-db ➠ npx babel-node db/migrations/202005-add-article-categories.js ResponseError: Response Error at IncomingMessage.&lt;anonymous&gt; (/Users/mrorz/workspace/rumors-db/node_modules/@elastic/elasticsearch/lib/Transport.js:296:25) at IncomingMessage.emit (events.js:205:15) at IncomingMessage.EventEmitter.emit (domain.js:471:20) at endReadableNT (_stream_readable.js:1137:12) at processTicksAndRejections (internal/process/task_queues.js:84:9) { name: 'ResponseError', meta: { body: { took: 4641, timed_out: false, total: 36068, updated: 3577, deleted: 0, batches: 4, version_conflicts: 423, noops: 0, retries: [Object], throttled_millis: 0, requests_per_second: -1, throttled_until_millis: 0, failures: [Array] }, statusCode: 409, headers: { 'content-type': 'application/json; charset=UTF-8', 'content-length': '133674' }, warnings: null, meta: { context: null, request: [Object], name: 'elasticsearch-js', connection: [Object], attempts: 1, aborted: false } } } ``` *2nd round* ``` 18:20 ≎ ~/workspace/rumors-db ➠ npx babel-node db/migrations/202005-add-article-categories.js { body: { took: 3650, timed_out: false, total: 36068, updated: 0, deleted: 0, batches: 37, version_conflicts: 0, noops: 36068, retries: { bulk: 0, search: 0 }, throttled_millis: 0, requests_per_second: -1, throttled_until_millis: 0, failures: [] }, statusCode: 200, headers: { 'content-type': 'application/json; charset=UTF-8', 'content-length': '239' }, warnings: null, meta: { context: null, request: { params: [Object], options: [Object], id: 1 }, name: 'elasticsearch-js', connection: { url: '<http://localhost:62222/>', id: '<http://localhost:62222/>', headers: {}, deadCount: 0, resurrectTimeout: 0, _openRequests: 0, status: 'alive', roles: [Object] }, attempts: 0, aborted: false } } ```
mrorz 18:23:52
沒有指定 op = ‘noop’ 的話是不是會無差別寫入呀
mrorz 18:24:44
第一次完全沒 `noop` ,但最新的 Articles 應該都要有 articleCategories
mrorz 18:28:53
關於「過去查過的訊息列表」的細節實作,規劃寫在這裡,請大家過目 @acerxp511 @lucien
https://g0v.hackmd.io/eIeU2g86Tfu5VnLazNfUvQ?both#Implementation-detail-2020510

主要要討論的東西有:
• 會新增一種 LIFF <> Chatbot GraphQL 的認證方式
• 如何用 google analytics utm_source 追蹤後續動作是來自「過去查過的訊息列表」的操作、並且記錄「過去查過的訊息列表」是「從 rich menu 點開」的還是「從 push message 點開」的
  • 👌1
2020-05-11
Sam Ho 09:54:55
@rongson has joined the channel
mrorz 13:13:41
Google search console 說下面這兩個頁面的點擊次數與上個月相比增加最多(+7.37K)
https://cofacts.g0v.tw/article/ui2q0uicd96a
https://cofacts.g0v.tw/article/u4qohr84huax

看來網友對把草莓插在樹上的影片很有興趣
mrorz 13:33:38
不過說到 Google,上週 Cofacts 配合 Youtube API Services Team 的 audit 調查,對方表示 Cofacts 在下面兩點不合格:

1. 沒有在 Privacy 與 ToS 中提及 Youtube ToS - Policy #: III.A.1,2
2. 沒有每 30 天就重新更新爬到的 non-authorized 資料 - Policy #: III.E.4.a-g
1 的部分我們可能會需要針對 Terms of use (LINE網站) 進行修正(加上 Youtube 相關敘述)之後,正式把它加到 Cofacts 官網上
2 的部分就就滿討厭的,不只技術上很煩,也會影響 Cofacts 的使用——一但原始謠言影片被下架,30 天內資料庫被強制更新,那個影片的 title 與 description 就會被洗掉。

我在 audit 時給的例子就很明顯,謠言本體剛好是個被下架的影片,拿另一個被重新上架的影片去查,還能查到對應的訊息:https://docs.google.com/document/d/1hlFszsLV9uUJl17L40o6AGZVtyAEoTcLA76wSz969QI/edit
(註: 步驟裡的那個 query 影片之前還活著,大概最近幾天也下架了⋯⋯)

Cofacts 之所以製作 URL preview,其中一個目的就是為了保存訊息被回報的當下,連結裡面的內容,以供未來查詢比對。如果要求我們每 30 天要更新,效果會大打折扣,以上面這個 COVID-19 陰謀論的例子來說,甚至會影響到 COVID-19 的防疫。

不知道大家有沒有什麼想法呢?
mrorz 13:35:11
1 的部分我們可能會需要針對 Terms of use (LINE網站) 進行修正(加上 Youtube 相關敘述)之後,正式把它加到 Cofacts 官網上
mrorz 13:42:06
Replied to a thread: 2020-05-11 13:33:38
2 的部分就就滿討厭的,不只技術上很煩,也會影響 Cofacts 的使用——一但原始謠言影片被下架,30 天內資料庫被強制更新,那個影片的 title 與 description 就會被洗掉。

我在 audit 時給的例子就很明顯,謠言本體剛好是個被下架的影片,拿另一個被重新上架的影片去查,還能查到對應的訊息:https://docs.google.com/document/d/1hlFszsLV9uUJl17L40o6AGZVtyAEoTcLA76wSz969QI/edit
(註: 步驟裡的那個 query 影片之前還活著,大概最近幾天也下架了⋯⋯)

Cofacts 之所以製作 URL preview,其中一個目的就是為了保存訊息被回報的當下,連結裡面的內容,以供未來查詢比對。如果要求我們每 30 天要更新,效果會大打折扣,以上面這個 COVID-19 陰謀論的例子來說,甚至會影響到 COVID-19 的防疫。

不知道大家有沒有什麼想法呢?
github 13:56:45
Replaced by `ListArticle`, no longer needs root-level article-reply list
github 14:10:34
As discussed in <https://g0v-slack-archive.g0v.ronny.tw/index/channel/C2PPMRQGP/2020-05#ts-1588494165.332500|Slack>, we are adding this to `ListArticleFilter`: ``` articleReplyFrom: { userId: String, appId: String, exists: Boolean } ``` In order to filter out articles that • contains my reply (for "我查核過" filter in reply (actually replied _article_) list) • does not contain my reply (for <https://g0v.hackmd.io/iJm9_nZaTA2GyInn7ycxoA|navbar "for you" numbering>) • contains a certain user's reply • contains no certain user's reply Relates to • <https://github.com/cofacts/rumors-api/issues/60|#60> • <https://github.com/cofacts/rumors-api/issues/35|#35> • <https://github.com/cofacts/rumors-site/pull/247#discussion_r419149777|cofacts/rumors-site#247 (comment)>
mrorz 14:14:00
Pass 的話不用理~
那個是在測 error handling 嗎?
github 14:15:34
Article should provide a field that returns analytics data for trend charts using <https://developers.google.com/analytics/devguides/reporting/core/v4|Google Analytics Reporting API> • Chatbot visit per day • Chatbot user count per day • Chatbot total visit in 30 days • Chatbot total user in 30 days • All above stats, but using Cofacts production site as target Related: • <https://www.figma.com/file/zpD45j8nqDB2XfA6m2QskO?node-id=889:306#25950845|https://www.figma.com/file/zpD45j8nqDB2XfA6m2QskO?node-id=889:306#25950845>
mrorz 14:15:40
Article should provide a field that returns analytics data for trend charts using <https://developers.google.com/analytics/devguides/reporting/core/v4|Google Analytics Reporting API> Related: • <https://www.figma.com/file/zpD45j8nqDB2XfA6m2QskO?node-id=889:306#25950845|https://www.figma.com/file/zpD45j8nqDB2XfA6m2QskO?node-id=889:306#25950845>
github 18:03:12
I eventually remove the LoadMore component and leverage the whole component to `AarticlePageLayout`, because with only `onClick` &amp; `loading` the LoadMore component seems similar to regular button, the actual logics are mostly move to `ArticlePageLayout`, it's a bit strange to have a Component call `LoadMore` while the actual load-more logics are written in upper component.
2020-05-12
github 02:21:21
Sorry, I forgot to consider the case when there is no "next" button: <https://user-images.githubusercontent.com/108608/81596572-e4ea0000-93f6-11ea-9841-4d63cce39514.png|image> (It's fine in mobile, though; it's just that floating action buttons are covering the last item)
github 02:21:21
Thanks for the modification! When clicking into detail pages &amp; back out, the scroll position and previously loaded pages all worked like a charm. Spotted some minor styling issue, not a blocker though.
mrorz 10:49:05
@ggm 有 conflict 唷

也可以參考一下我的 Comment ~
mrorz 10:59:05
Now on staging:
• API: https://github.com/cofacts/rumors-api/pull/153
• Website: dev branch (infinte scroll pending,稍晚 deploy)
• Chatbot: https://github.com/cofacts/rumors-line-bot/pull/184 (連環 7 PR 的最後一個,還有 4 個沒人 review)
我週三想要把這些都推上 production 來收 user feedback,比較能知道目前為止的這些大變動,有哪些需要改進的地方。推上去之後可能會有一兩週的 error 爆炸期。

網站、chatbot 的 error quota 還有滿多的,我覺得可以接得住 error。
• Staging site: https://cofacts.hacktabl.org/articles
• Staging line bot: https://lin.ee/1QUzEX4nI
大家覺得如何呢?有什麼 release blocker 嗎?
我是覺得 staging 的東西放一陣子了,都沒有人有啥意見,就算去 Facebook 社團請人到 staging 點一點,也應該不會有什麼人試。

早晚都要上 production,不如就直接放上去,才能收到真實的 feedback 或抱怨~
我想有一種做法是

production 放上 staging 的版本、保留原本 production 的 image,如果有很嚴重的問題的話就隨時 revert back 到原本的 production
如果沒問題的話就 merge 這些 PR、build 正式的 docker image label (其實 staging 跟 production 的 docker image 根本是同一份 code,只是 label 不同,並沒有 build-time config⋯⋯)
全民公測可能也不錯
我今天會把 chatbot 與網站英文翻中文
不過還是注意到一些網站的 release blocker:

• ~Article detail 裡的 upvote / downvote 已經被更新為新樣式,但是他是壞的(component prop breaking change?)~ PR249 中已修正
• 文章列表至少應該要有「熱門回報」filter,亦即現在 production 上的「僅顯示兩個回報以上」的功能
• 「等你來答」目前 filter 跟 spec 不符,功能跟 article list 預設目前一致
我覺得至少以上三點要修正,release 比較有意義 @@

Spec 與 mockup 放在:
https://g0v.hackmd.io/0xqZVqKiSYaIeSYFtT5NRg?view#Versions--release-scope
@lucien 針對 chatbot 的連環 PR,現況是
• 我的 PR 會與 GGM database 的部分有 conflict,先 merge 一個進去會比較好處理
• 尚未 review 的 PR 就算有東西要改,在過長的連環 PR 底下要改也很麻煩
我建議我的 chatbot PR 先全部 merge,有問題之後另外開 PR,這樣比較能 unblock 之後的開發與 deploy?
cc/ @ggm @acerxp511
我投先 merge 一票
conflict 我修掉哩
先 merge ok
全民公測也 ok
@acerxp511 @ggm dev merge 完囉,請把你們的 branch rebase 到最新的 dev
感謝感謝
mrorz 11:13:06
我是覺得 staging 的東西放一陣子了,都沒有人有啥意見,就算去 Facebook 社團請人到 staging 點一點,也應該不會有什麼人試。

早晚都要上 production,不如就直接放上去,才能收到真實的 feedback 或抱怨~
mrorz 11:19:23
我想有一種做法是

production 放上 staging 的版本、保留原本 production 的 image,如果有很嚴重的問題的話就隨時 revert back 到原本的 production
mrorz 11:20:36
如果沒問題的話就 merge 這些 PR、build 正式的 docker image label (其實 staging 跟 production 的 docker image 根本是同一份 code,只是 label 不同,並沒有 build-time config⋯⋯)
lucien 14:05:07
全民公測可能也不錯
2020-05-13
mrorz 11:16:24
Coscup我們能報哪一個議程呀
@lucien 已經截止囉。有個 open source chatbot track,但我變不出跟去年不同的內容,所以沒投。
mrorz 13:16:38
Replied to a thread: 2020-05-12 10:59:05
我今天會把 chatbot 與網站英文翻中文
不過還是注意到一些網站的 release blocker:

• ~Article detail 裡的 upvote / downvote 已經被更新為新樣式,但是他是壞的(component prop breaking change?)~ PR249 中已修正
• 文章列表至少應該要有「熱門回報」filter,亦即現在 production 上的「僅顯示兩個回報以上」的功能
• 「等你來答」目前 filter 跟 spec 不符,功能跟 article list 預設目前一致
我覺得至少以上三點要修正,release 比較有意義 @@

Spec 與 mockup 放在:
https://g0v.hackmd.io/0xqZVqKiSYaIeSYFtT5NRg?view#Versions--release-scope
lucien 14:32:10
Coscup我們能報哪一個議程呀
mrorz 15:18:41
@lucien 已經截止囉。有個 open source chatbot track,但我變不出跟去年不同的內容,所以沒投。
mrorz 15:30:54
現在 staging site 上的是 @yanglin5689446PR-249 的內容唷

有新的 article detail:https://cofacts.hacktabl.org/article/1fiten3qki74s
直連 article detail 會 time-out,看起來是遇到了之前 yanglin 回報的 `requestedForReply` 欄位很慢的問題
https://g0v-tw.slack.com/archives/C2PPMRQGP/p1588665581350300
@lucien @stbb1025 實際用起來才發現,現在「寫新回應」區塊放在「現有回應」上面,可能會讓使用者以為沒有新回應,就自己寫了一個 XD

那個區塊是可以收合的嗎? wireframe 與 mockup 好像沒有標記。而且如果可以收合,也不知道如何把該區塊「合起來」。

cc/ @yanglin5689446
咦?不是點查核闢謠才會展開嗎
可能因為沒標所以 https://cofacts.hacktabl.org/articles 目前實作是開開的 XD

另外點開之後要怎麼關起來呢?再點一次「查核闢謠」按鈕?
好XD我再補一下
點了之後不用關起來吧?如果照你說的情境,目前的設計他們一定是有看到下面回應才點開的
是點擊才出現
Hmm 那先讓他預設收合後再看看好了
我也是實際在網站上看了之後才發現,使用者有可能看完訊息就想回這件事情

希望預設收合之後,「現有回應」的可見度就會變高。
所以寫新回應的區塊
手機版跟 PC 版一致都是點了 `查核闢謠` 按鈕之後才打開嗎?
手機版會切換到新頁面唷
PC 與手機都是點了按鈕之後才會有查核闢謠的區塊或全版 modal

手機要不要換 URL 可以討論
換網址的話就能按手機的 back
不換網址的話可能還是要處理一下手機的 back 避免誤按(或保留現在的自動儲存功能)
我私心覺得兩個都可以換網址一下
這樣我在做的時候可以固定網址 hot relaod 不用一直點開 XD
啊 不過 next router 我不熟 QQ
我非常懷疑 next-router 是否能做到這種 full-screen modal 換 URL 但原頁面後面的資料全數保留的事情
可以 google 一下有沒有人做過
說不定其實可以這樣

```// pages/article/[id].js

export default DefaultArticlePage() {
// ...
return <ArticlePage showCreate={false} />
}

// pages/article/[id]/create.js

export default ArticlePageWithCreate() {
// ...
return <ArticlePage showCreate={true} />
}```
然後直接用 next-router `Link` 換頁之類的

因為是兩個相同的 component 所以或許 react reconcile 時會發現是同一個 component 所以不會重新 mount

但可能要測一下
雖然這樣做要小心頁面裡的所有 URL 絕對不能用相對路徑,不然 `/article/xxx` 到 `/article/xxx/create` 會導致相對路徑對歪 XD
mrorz 15:34:39
直連 article detail 會 time-out,看起來是遇到了之前 yanglin 回報的 `requestedForReply` 欄位很慢的問題
https://g0v-tw.slack.com/archives/C2PPMRQGP/p1588665581350300
yanglin
不知道為什麼 `GetArticle` 只要 query `requestedForReply` 就會變得很慢欸 0.0
github 15:40:52
Deployed to <https://cofacts.hacktabl.org/articles|https://cofacts.hacktabl.org/articles> so that everyone can inspect. <https://hub.docker.com/r/cofacts/rumors-site/tags|Docker image tags>: `pr249-en` and `pr249-tw`
mrorz 15:49:37
@lucien 針對 chatbot 的連環 PR,現況是
• 我的 PR 會與 GGM database 的部分有 conflict,先 merge 一個進去會比較好處理
• 尚未 review 的 PR 就算有東西要改,在過長的連環 PR 底下要改也很麻煩
我建議我的 chatbot PR 先全部 merge,有問題之後另外開 PR,這樣比較能 unblock 之後的開發與 deploy?
cc/ @ggm @acerxp511
mrorz 16:03:50
@lucien @stbb1025 實際用起來才發現,現在「寫新回應」區塊放在「現有回應」上面,可能會讓使用者以為沒有新回應,就自己寫了一個 XD

那個區塊是可以收合的嗎? wireframe 與 mockup 好像沒有標記。而且如果可以收合,也不知道如何把該區塊「合起來」。

cc/ @yanglin5689446
stbb1025 16:05:53
咦?不是點查核闢謠才會展開嗎
  • 😮1
mrorz 16:07:20
可能因為沒標所以 https://cofacts.hacktabl.org/articles 目前實作是開開的 XD

另外點開之後要怎麼關起來呢?再點一次「查核闢謠」按鈕?
stbb1025 16:09:03
好XD我再補一下
點了之後不用關起來吧?如果照你說的情境,目前的設計他們一定是有看到下面回應才點開的
lucien 16:10:42
是點擊才出現
mrorz 16:11:00
Hmm 那先讓他預設收合後再看看好了
mrorz 16:11:39
我也是實際在網站上看了之後才發現,使用者有可能看完訊息就想回這件事情

希望預設收合之後,「現有回應」的可見度就會變高。
nonumpa 16:12:40
我投先 merge 一票
  • 1
stbb1025 17:55:04
今天想討論一下這樣的landing page走向o不ok?
ok的話我就繼續把後面內容做完
(這版的寬是1024)
landing_page_20200513.png
  • ❤️4
  • 🐳1
  • 🦒1
@bil @mrorz @lucien
(努力想像首圖英文版怎麼處理)
我覺得很有趣~~
「大家都想知道」是最近新謠言嗎
可以幫我翻譯一下嗎我就直接排XD
是熱門謠言,但我想跟大家討論一下要揭露哪些訊息
Got a hoax in messaging app?
Was it a FAKE NEWS?
Got something suspicious in messaging apps?
You may:
Copy & paste the suspicious content here
[Look it UP]
Was it a FAKE NEWS? ->我喜歡這個 ❤️
f-word XD

媒體是好朋友,用 f-word 感覺會讓媒體有戒心
原來有這層考量 XD
stbb1025 17:59:25
@bil @mrorz @lucien
mrorz 17:59:42
(努力想像首圖英文版怎麼處理)
mrorz 17:59:57
我覺得很有趣~~
mrorz 18:00:06
「大家都想知道」是最近新謠言嗎
stbb1025 18:00:07
可以幫我翻譯一下嗎我就直接排XD
stbb1025 18:01:46
是熱門謠言,但我想跟大家討論一下要揭露哪些訊息
mrorz 18:02:57
Got a hoax in messaging app?
  • ❤️1
bil 18:03:14
Was it a FAKE NEWS?
  • ❤️1
mrorz 18:04:31
Got something suspicious in messaging apps?
  • ❤️1
ggm 18:06:05
真怪我之前有 rebase 了說,我又 rebase 然後 push 囉
bil 18:06:07
You may:
Copy & paste the suspicious content here
[Look it UP]
  • ❤️1
ggm 18:06:40
conflict 我修掉哩
ggm 18:07:18
先 merge ok
  • 1
ggm 18:08:01
全民公測也 ok
stbb1025 18:09:16
Was it a FAKE NEWS? ->我喜歡這個 ❤️
mrorz 18:25:04
我剛才在整理現在 staging 與 spec 不一樣的地方,然後發現 spec 有個地方我也不太清楚。

想問 @lucien spec 的 article list(可疑訊息)reply list(最新查核)的 default filter 分別是什麼呢?會有「什麼 filter 都沒有」的狀況嗎?如果「什麼 filter 都沒有」,那 article list 與 reply list 的差別會是什麼呢?
@lucien 可以說明一下 article list(可疑訊息) 與 reply list(最新查核)的 default filter 分別是什麼呢?
Default 沒有filter 但有 sort
@lucien 所以「最新查核」會顯示「尚未被回應」的文章嗎~?

https://g0v.hackmd.io/@NFi0czulSemxCM8RNSlz8Q/HJ8xT3QVU/%2FZZWHWi2BTuyhkSWzAvKLAw spec 沒說不行唷。
所以目前也實作成了這樣,尚無回應的文章也可能會列在「最新查核」
最新查核只會出現有查核過的文章
一定是至少有一篇回應的
現在最新查核也是從 article 下去 filter 而不是 reply 對嗎
已更新
不過這是不會出現在選項上的 filter
感謝更新,這可能要開一張新票來處理

目前 @yanglin5689446 實作的 `defaultFilter` 機制可能要稍微改一下,才能支援這種「不會出現在選項上、關不掉的 filter」
(現在的 `defaultFilter` 可以被新設的 filter 覆蓋)
mrorz 20:07:49
@acerxp511 @ggm dev merge 完囉,請把你們的 branch rebase 到最新的 dev
感謝感謝
  • 💪1
delightfullychaotic 21:42:15
請問開會是開完了嗎?我一直找不到連結QQ
delightfullychaotic 2020-05-13 22:00:22
結果有剛剛按個mute 就默默又沒有了== Howie真的不用負責嗎
但我也不知道怎麼重現 XD 他只能通靈
或許我們的 powercall room 很有靈氣
比較容易重現 bug
你可以跟他約在那個 room
說不定就能重現
nonumpa 21:46:03
tico.chat
The considerate messenger. Chat with whom you care about always at the best moments and times
delightfullychaotic 22:00:22
結果有剛剛按個mute 就默默又沒有了== Howie真的不用負責嗎
Further 23:27:27
@i.further.54 has joined the channel
mrorz 23:52:26
但我也不知道怎麼重現 XD 他只能通靈
mrorz 23:53:31
或許我們的 powercall room 很有靈氣
比較容易重現 bug
你可以跟他約在那個 room
說不定就能重現
mrorz 23:54:17
f-word XD

媒體是好朋友,用 f-word 感覺會讓媒體有戒心
2020-05-14
mrorz 02:38:48
Figma
Created with Figma
https://cofacts.kktix.cc/events/cofacteditor20 請看看有沒有 misinformation (?)
頭到砲管的很迷因耶,不用嗎
那張是直的 超難排
這樣嗎
是說怎麼看得出來那是砲管 w
哈哈哈哈
所以要放哪張 wwwww
Model 決定@bil
@delightfullychaotic 麻煩你在在地社團散佈惹 m(_ _)m https://cofacts.kktix.cc/events/cofacteditor20
delightfullychaotic 2020-05-16 15:40:47
我覺得有model正面更好!
yanglin 08:24:38
所以寫新回應的區塊
手機版跟 PC 版一致都是點了 `查核闢謠` 按鈕之後才打開嗎?
stbb1025 09:17:52
手機版會切換到新頁面唷
bil 11:15:04
CC
  • 2
mrorz 11:17:13
PC 與手機都是點了按鈕之後才會有查核闢謠的區塊或全版 modal

手機要不要換 URL 可以討論
換網址的話就能按手機的 back
不換網址的話可能還是要處理一下手機的 back 避免誤按(或保留現在的自動儲存功能)
mrorz 11:34:13
https://cofacts.kktix.cc/events/cofacteditor20 請看看有沒有 misinformation (?)
cofacts.kktix.cc
2 個月一次,用一個下午與 Cofacts 一起工作闢謠解惑,讓不同意見突破同溫層。 來編輯小聚就送限量 Cofacts 貼紙。回應超過200篇,送委外設計LINE貼圖!感謝好想工作室 提供場地。
github 11:55:52
Staging DB provisioned on Heroku, let's give it a shot <https://user-images.githubusercontent.com/108608/81890969-d740a000-95d9-11ea-91ac-8a389767eb25.png|螢幕快照 2020-05-14 上午11 52 59>
stbb1025 12:03:57
原來有這層考量 XD
mrorz 12:13:55
Replied to a thread: 2020-05-05 15:59:41
我發現 5/4 更新 staging API 成 https://github.com/cofacts/rumors-api/pull/153 的版本之後,5/5 @yanglin5689446 就回報問題

朝 API 裡面 elasticsearch result 沒接好的方向調查 ing
  • 👌1
mrorz 14:19:44
結果不是 elasticsearch result 沒接好
而是
1. 一般來說,跟使用者相關的 object field 如 `ownVote` 等等,在沒有指定 userId (如:SSR) 時,會直接回傳 null。然而, `requestedForReply` 錯誤地使用了只有 mutation API 該用的 `assertUser({...})` ,所以只要有 requestedForReply 就會產生 error。
mrorz 14:20:21
image.png
mrorz 14:21:02
image.png
mrorz 14:22:16
2. 我們用的 apollo-link-error 如果有 exception 的話,他就會無聲無息地悶不吭聲,讓後面的 apollo-client resolve 一整個卡住。我是在 `BatchHttpLink` 與 `onError` 中間插了一個印 console 的 link 才能抓到那個 error。

目前我們的 `onError` 裡頭用了 `alert()`,在 NodeJS 環境下並無此 function,所以他其實應該在 runtime 時遇到了 Reference error,但卻無聲無息。
lucien 14:49:42
頭到砲管的很迷因耶,不用嗎
github 14:57:26
During SSR, an GraphQL Error can halt the processing of Apollo-link middlewares, causing the server-side render to return nothing. This PR 1. Always print the error to console 2. Fix SSR by detecting if `alert` exists in the global namespace (it's `undefined` during NodeJS SSR) From discussion: <https://g0v-tw.slack.com/archives/C2PPMRQGP/p1589437262448200?thread_ts=1588665581.350300&amp;cid=C2PPMRQGP|https://g0v-tw.slack.com/archives/C2PPMRQGP/p1589437262448200?thread_ts=1588665581.350300&amp;cid=C2PPMRQGP> &gt; 2. 我們用的 apollo-link-error 如果有 exception 的話,他就會無聲無息地悶不吭聲,讓後面的 apollo-client resolve 一整個卡住。我是在 BatchHttpLink 與 onError 中間插了一個印 console 的 link 才能抓到那個 error。 &gt; 目前我們的 onError 裡頭用了 alert(),在 NodeJS 環境下並無此 function,所以他其實應該在 runtime 時遇到了 Reference error,但卻無聲無息。
yanglin 15:13:49
我私心覺得兩個都可以換網址一下
這樣我在做的時候可以固定網址 hot relaod 不用一直點開 XD
yanglin 15:38:12
啊 不過 next router 我不熟 QQ
mrorz 15:48:31
至於 apollo-link-error 把 runtime error 吃掉這件事情,我在 codesandbox 上卻無法重現

https://codesandbox.io/s/awesome-shamir-vpf2d?file=/index.js
mrorz 15:50:01
看起來 error 有好好地被抓出來 @@
image.png
github 16:52:46
Currently, `requestedForReply` of `Article` object type would throw error if the user is not logged-in. However, other fields that requires authentication (such as <https://github.com/cofacts/rumors-api/blob/master/src/graphql/models/ArticleReply.js#L68-L89|`ownVote`>, <https://github.com/cofacts/rumors-api/blob/master/src/graphql/models/ArticleReply.js#L42-L51|`canUpdateStatus`>) just returns `null` if the user is not logged in. `assertUser()` is meant for mutation resolvers and ordinary object type resolvers should avoid using `assertUser()`. Related discussion: • <https://g0v-tw.slack.com/archives/C2PPMRQGP/p1588665581350300|https://g0v-tw.slack.com/archives/C2PPMRQGP/p1588665581350300> • <https://g0v-slack-archive.g0v.ronny.tw/index/channel/C2PPMRQGP#ts-1588665581.350300|https://g0v-slack-archive.g0v.ronny.tw/index/channel/C2PPMRQGP#ts-1588665581.350300>
mrorz 16:55:08
我非常懷疑 next-router 是否能做到這種 full-screen modal 換 URL 但原頁面後面的資料全數保留的事情
可以 google 一下有沒有人做過
  • 😢1
mrorz 16:56:47
說不定其實可以這樣

```// pages/article/[id].js

export default DefaultArticlePage() {
// ...
return <ArticlePage showCreate={false} />
}

// pages/article/[id]/create.js

export default ArticlePageWithCreate() {
// ...
return <ArticlePage showCreate={true} />
}```
然後直接用 next-router `Link` 換頁之類的

因為是兩個相同的 component 所以或許 react reconcile 時會發現是同一個 component 所以不會重新 mount

但可能要測一下
mrorz 16:59:44
雖然這樣做要小心頁面裡的所有 URL 絕對不能用相對路徑,不然 `/article/xxx` 到 `/article/xxx/create` 會導致相對路徑對歪 XD
mrorz 17:00:47
那張是直的 超難排
mrorz 17:13:35
這樣嗎
image.png
mrorz 17:15:15
是說怎麼看得出來那是砲管 w
lucien 17:30:46
哈哈哈哈
github 18:25:51
No idea why pr keeps failing no matter how many times I rebuild. `CreateReply` test cleanup always fails. On my local machine, I can reproduce the error for the first time I merge the code, but cannot reproduce at all after the first run. Soooo weird.
mrorz 21:48:13
所以要放哪張 wwwww
lucien 22:15:22
Model 決定@bil
mrorz 22:59:31
cofacts.kktix.cc
2 個月一次,用一個下午與 Cofacts 一起工作闢謠解惑,讓不同意見突破同溫層。 來編輯小聚就送限量 Cofacts 貼紙。回應超過200篇,送委外設計LINE貼圖!感謝好想工作室 提供場地。
2020-05-15
yanglin 11:30:59
想問一下上面的那個篩選
我原本以為他只能單選(舊的邏輯也是單選)
不過從新的邏輯上來看這幾個選項是沒有重疊的
所以他們是可以多選的嗎?
image.png
lucien 11:35:27
是設計成可以多選的
  • 👌2
github 14:23:28
Currently if we click "Share" on desktop with a narrow browser screen, the share button does nothing. Let's combine `openMenu` and `handleShare`: • If `navigator.share` exists, use then invoke `navigator.share` • Otherwise, perform logic in `openMenu` We would only need one button this way
github 14:23:28
Looks like something worth joining <http://cofacts.hacktabl.org/storybook/index.html|http://cofacts.hacktabl.org/storybook/index.html> Otherwise we may forget about this component :P
github 14:23:28
`px` incorrect; you may also need to adjust `py` taking its content into consideration *as-is* <https://user-images.githubusercontent.com/108608/81927501-50f67f00-9616-11ea-9497-a4f8c51f00c1.png|image> *to-be* Spec: <https://www.figma.com/file/zpD45j8nqDB2XfA6m2QskO/Cofacts-website?node-id=889%3A306|https://www.figma.com/file/zpD45j8nqDB2XfA6m2QskO/Cofacts-website?node-id=889%3A306> <https://user-images.githubusercontent.com/108608/81927523-5eac0480-9616-11ea-9321-bd2d0385e6eb.png|image>
github