From c22614096cc6562a8581ff6d13af141370802b8e Mon Sep 17 00:00:00 2001 From: Yiqing Hua Date: Fri, 27 Jul 2018 16:18:23 +0000 Subject: [PATCH 1/9] changing bfs to dfs --- crowdsourcing/wpconvlib/src/conversation.ts | 14 +++++----- .../conv-viewer-webapi/src/routes.ts | 27 ++++++++++++------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/crowdsourcing/wpconvlib/src/conversation.ts b/crowdsourcing/wpconvlib/src/conversation.ts index 1effdcdc..f86eb395 100644 --- a/crowdsourcing/wpconvlib/src/conversation.ts +++ b/crowdsourcing/wpconvlib/src/conversation.ts @@ -170,14 +170,12 @@ export function htmlForComment( export function walkDfsComments( rootComment: Comment, f: (c: Comment) => void): void { const commentsHtml = []; - let agenda: Comment[] = []; let nextComment: Comment|undefined = rootComment; - while (nextComment) { - if (nextComment.children) { - agenda = agenda.concat(nextComment.children); - } + if (nextComment) { f(nextComment); - nextComment = agenda.pop(); + for (const ch of nextComment.children) { + walkDfsComments(ch, f) + } } } @@ -207,7 +205,8 @@ export function makeParent(comment: Comment, parent: Comment) { parent.children = []; } parent.children.push(comment); - parent.children.sort(compareCommentOrderSmallestFirst); + parent.children.sort(compareCommentOrder); + console.error('Children of ', parent.id, parent.children.map((value, index) => value.id)) comment.parent_id = parent.id; comment.isRoot = false; } @@ -253,6 +252,7 @@ export function structureConversaton(conversation: Conversation): Comment|null { items.push({key: k, value: conversation[k]}); } items.sort((v1, v2) => -compareByDateFn(v1.value, v2.value)); + console.error(items.map((value, index) => value.key)); const ids = items.map((value, index) => value.key); let rootComment: Comment|null = null; diff --git a/view_conversations/conv-viewer-webapi/src/routes.ts b/view_conversations/conv-viewer-webapi/src/routes.ts index f46cb53c..6768cb69 100644 --- a/view_conversations/conv-viewer-webapi/src/routes.ts +++ b/view_conversations/conv-viewer-webapi/src/routes.ts @@ -75,10 +75,17 @@ export function setup( runtime_types.CommentId.assert(req.params.comment_id); // TODO remove outer try wrapper unless it get used. - const sqlQuery = `SELECT * - FROM ${table} - WHERE id="${comment_id}" - LIMIT 100`; + // id field is unique. + const sqlQuery = ` + SELECT r.* + FROM ${table} as r + JOIN + (SELECT conversation_id, timestamp + FROM ${table} + WHERE id="${comment_id}") as l + ON r.conversation_id=l.conversation_id + WHERE r.timestamp <= l.timestamp + `; // Query options list: // https://cloud.google.com/spanner/docs/getting-started/nodejs/#query_data_using_sql const query: spanner.Query = { @@ -87,7 +94,7 @@ export function setup( await spannerDatabase.run(query).then(results => { const rows = results[0]; - res.status(httpcodes.OK).send(db_types.parseOutputRows(rows)); + res.status(httpcodes.OK).send(db_types.parseOutputRows(rows)); }); } catch (e) { console.error(`*** Failed: `, e); @@ -115,7 +122,7 @@ export function setup( await spannerDatabase.run(query).then(results => { const rows = results[0]; - res.status(httpcodes.OK).send(db_types.parseOutputRows(rows)); + res.status(httpcodes.OK).send(db_types.parseOutputRows(rows)); }); } catch (e) { console.error(`*** Failed: `, e); @@ -144,7 +151,7 @@ export function setup( await spannerDatabase.run(query).then(results => { const rows = results[0]; - res.status(httpcodes.OK).send(db_types.parseOutputRows(rows)); + res.status(httpcodes.OK).send(db_types.parseOutputRows(rows)); }); } catch (e) { console.error(`*** Failed: `, e); @@ -162,7 +169,7 @@ export function setup( // TODO remove outer try wrapper unless it get used. const sqlQuery = `SELECT * FROM ${table} - WHERE page_title LIKE "${page_title}" + WHERE page_title = "${page_title}" LIMIT 100`; // Query options list: // https://cloud.google.com/spanner/docs/getting-started/nodejs/#query_data_using_sql @@ -173,7 +180,7 @@ export function setup( await spannerDatabase.run(query).then(results => { const rows = results[0]; - res.status(httpcodes.OK).send(db_types.parseOutputRows(rows)); + res.status(httpcodes.OK).send(db_types.parseOutputRows(rows)); }); } catch (e) { console.error(`*** Failed: `, e); @@ -218,7 +225,7 @@ export function setup( await spannerDatabase.run(query).then(results => { const rows = results[0]; - res.status(httpcodes.OK).send(db_types.parseOutputRows(rows)); + res.status(httpcodes.OK).send(db_types.parseOutputRows(rows)); }); } catch (e) { console.error(`*** Failed: `, e); From c3a90097d96d1d0b84752fd26208b24173573ade Mon Sep 17 00:00:00 2001 From: Yiqing Hua Date: Fri, 27 Jul 2018 16:21:05 +0000 Subject: [PATCH 2/9] - debugging output --- crowdsourcing/wpconvlib/src/conversation.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/crowdsourcing/wpconvlib/src/conversation.ts b/crowdsourcing/wpconvlib/src/conversation.ts index f86eb395..2677d807 100644 --- a/crowdsourcing/wpconvlib/src/conversation.ts +++ b/crowdsourcing/wpconvlib/src/conversation.ts @@ -206,7 +206,6 @@ export function makeParent(comment: Comment, parent: Comment) { } parent.children.push(comment); parent.children.sort(compareCommentOrder); - console.error('Children of ', parent.id, parent.children.map((value, index) => value.id)) comment.parent_id = parent.id; comment.isRoot = false; } From b489d4dbb494d5db5ff92c1698767e38de7e2cbb Mon Sep 17 00:00:00 2001 From: Yiqing Hua Date: Fri, 27 Jul 2018 19:23:26 +0000 Subject: [PATCH 3/9] speed up by change spanner indes --- crowdsourcing/wpconvlib/src/conversation.ts | 42 +++++++++++++---- .../src/testdata/example_conversations.ts | 46 +++++++++---------- .../conv-viewer-webapi/README.md | 6 +-- .../server_config.template.json | 8 ++-- .../conv-viewer-webapi/src/db_types.ts | 7 ++- .../conv-viewer-webapi/src/routes.ts | 19 ++++---- 6 files changed, 78 insertions(+), 50 deletions(-) diff --git a/crowdsourcing/wpconvlib/src/conversation.ts b/crowdsourcing/wpconvlib/src/conversation.ts index 2677d807..ea329d94 100644 --- a/crowdsourcing/wpconvlib/src/conversation.ts +++ b/crowdsourcing/wpconvlib/src/conversation.ts @@ -15,7 +15,7 @@ limitations under the License. */ export interface Comment { id: string; - comment_type: 'MODIFICATION'|'ADDITION'|'CREATION'|'RESTORATION'|'DELETION'; + type: 'MODIFICATION'|'ADDITION'|'CREATION'|'RESTORATION'|'DELETION'; content: string; cleaned_content: string; parent_id: string|null; @@ -39,6 +39,31 @@ export interface Comment { latestVersion?: string | null; // id of the latest version of this comment if isPresent // is false, otherwise id of self. dfs_index?: number // index according to Depth First Search of conv. + // Starting here are toxicity scores, since they don't exist in all datasets + // except in English, this fields are optional. + RockV6_1_FLIRTATION?: number | null, + RockV6_1_GENDER?: number | null, + RockV6_1_HEALTH_AGE_DISABILITY?: number | null, + RockV6_1_RELIGION?: number | null, + RockV6_1_RNE?: number | null, + RockV6_1_SEVERE_TOXICITY?: number | null, + RockV6_1_SEXUAL_ORIENTATION?: number | null, + RockV6_1_SEXUALLY_EXPLICIT?: number | null, + RockV6_1_TOXICITY?: number | null, + RockV6_1_TOXICITY_IDENTITY_HATE?: number | null, + RockV6_1_TOXICITY_INSULT?: number | null, + RockV6_1_TOXICITY_OBSCENE?: number | null, + RockV6_1_TOXICITY_THREAT?: number | null, + Smirnoff_2_ATTACK_ON_AUTHOR?: number | null, + Smirnoff_2_ATTACK_ON_COMMENTER?: number | null, + Smirnoff_2_ATTACK_ON_PUBLISHER?: number | null, + Smirnoff_2_INCOHERENT?: number | null, + Smirnoff_2_INFLAMMATORY?: number | null, + Smirnoff_2_LIKELY_TO_REJECT?: number | null, + Smirnoff_2_OBSCENE?: number | null, + Smirnoff_2_OFF_TOPIC?: number | null, + Smirnoff_2_SPAM?: number | null, + Smirnoff_2_UNSUBSTANTIAL?: number | null, } export interface Conversation { [id: string]: Comment } @@ -173,8 +198,10 @@ export function walkDfsComments( let nextComment: Comment|undefined = rootComment; if (nextComment) { f(nextComment); - for (const ch of nextComment.children) { - walkDfsComments(ch, f) + if (nextComment.children) { + for (const ch of nextComment.children) { + walkDfsComments(ch, f) + } } } } @@ -251,7 +278,6 @@ export function structureConversaton(conversation: Conversation): Comment|null { items.push({key: k, value: conversation[k]}); } items.sort((v1, v2) => -compareByDateFn(v1.value, v2.value)); - console.error(items.map((value, index) => value.key)); const ids = items.map((value, index) => value.key); let rootComment: Comment|null = null; @@ -262,13 +288,13 @@ export function structureConversaton(conversation: Conversation): Comment|null { // If the action is deletion, the content must have been deleted. conversation[i].isPresent = true; conversation[i].latestVersion = i; - if (comment.comment_type === 'DELETION') { + if (comment.type === 'DELETION') { conversation[i].isPresent = false; conversation[i].latestVersion = null; } if (comment.parent_id !== null && comment.parent_id !== '' && conversation[comment.parent_id]) { - if (comment.comment_type !== 'RESTORATION') { + if (comment.type !== 'RESTORATION') { conversation[comment.parent_id].isPresent = false; } else { conversation[i].isPresent = false; @@ -277,7 +303,7 @@ export function structureConversaton(conversation: Conversation): Comment|null { } // When a modification happens, the current comment will // be replaced by the new version. - if (comment.comment_type === 'MODIFICATION') { + if (comment.type === 'MODIFICATION') { conversation[comment.parent_id].latestVersion = i; } } @@ -285,7 +311,7 @@ export function structureConversaton(conversation: Conversation): Comment|null { for (const i of ids) { const comment = conversation[i]; - if (comment.comment_type === "RESTORATION" || comment.comment_type === "DELETION") { + if (comment.type === "RESTORATION" || comment.type === "DELETION") { continue; } comment.isFinal = false; diff --git a/crowdsourcing/wpconvlib/src/testdata/example_conversations.ts b/crowdsourcing/wpconvlib/src/testdata/example_conversations.ts index d15c25f8..f6aa1e4e 100755 --- a/crowdsourcing/wpconvlib/src/testdata/example_conversations.ts +++ b/crowdsourcing/wpconvlib/src/testdata/example_conversations.ts @@ -7,7 +7,7 @@ export const example_conversation1: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": ": Actually, based on my research, I did not find any unifid traslation. All the articles used the english term directly followed a parenthetical explaination. it's not convinent to use a chinese traslation, because it needs around 20 chinese characters to define the meaning of LGBT. I thought it is forbidden here using an english expression as title of a item. So, thanks your response. -- [[User:vegie|vegie]] 19:44 2004年5月31日\n", - "comment_type": "ADDITION", + "type": "ADDITION", "timestamp": "2004-05-31T10:44:26.000Z", "parent_id": null, "cleaned_content": " Actually, based on my research, I did not find any unifid traslation. All the articles used the english term directly followed a parenthetical explaination. it's not convinent to use a chinese traslation, because it needs around 20 chinese characters to define the meaning of LGBT. I thought it is forbidden here using an english expression as title of a item. So, thanks your response. 19:44 2004年5月31日", @@ -19,7 +19,7 @@ export const example_conversation1: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Sunzx", "content": ":: 用英文题目应该没问题的. 不过最好在文章里面说明\"此条目名称尚未有统一的中文翻译\"之类的... --[[User:Sunzx|Sunzx]] ([[User_talk:Sunzx|Talk]])[[北京大学| ]] 10:46 2004年5月31日 (UTC)\n", - "comment_type": "ADDITION", + "type": "ADDITION", "timestamp": "2004-05-31T10:46:48.000Z", "parent_id": null, "cleaned_content": " 用英文题目应该没问题的. 不过最好在文章里面说明\"此条目名称尚未有统一的中文翻译\"之类的... 10:46 2004年5月31日 (UTC)", @@ -32,7 +32,7 @@ export const example_conversation1: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": "==Translation Problem==\n", - "comment_type": "CREATION", + "type": "CREATION", "timestamp": "2004-05-31T06:03:04.000Z", "parent_id": null, "cleaned_content": "Translation Problem", @@ -45,7 +45,7 @@ export const example_conversation1: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". --[[User:Wing|Wing]] 15:02 2004年31日\n", - "comment_type": "ADDITION", + "type": "ADDITION", "timestamp": "2004-05-31T06:03:04.000Z", "parent_id": null, "cleaned_content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". 15:02 2004年31日", @@ -58,7 +58,7 @@ export const example_conversation1: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". --[[User:Vegie|Vegie]] 15:02 2004年31日\n", - "comment_type": "MODIFICATION", + "type": "MODIFICATION", "timestamp": "2004-05-31T06:25:49.000Z", "parent_id": "99858.17811.17782", "cleaned_content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". 15:02 2004年31日", @@ -71,7 +71,7 @@ export const example_conversation1: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Menchi", "content": ": If Chinese speakers didn't bother to invent a translate and most Chinese decided to just use the English term, then we can too. It sounds like it's the case with LGBT -- that there's no common Chinese translation? Then it's fine. It's kind of like how the word \"OK\" has been absorbed into daily spoken Chinese I guess. --[[User:Menchi|Menchi]] ([[User talk:Menchi|討論頁]])[[前247年|Â]] 08:14 2004年5月31日 (UTC)\n", - "comment_type": "ADDITION", + "type": "ADDITION", "timestamp": "2004-05-31T08:14:37.000Z", "parent_id": null, "cleaned_content": " If Chinese speakers didn't bother to invent a translate and most Chinese decided to just use the English term, then we can too. It sounds like it's the case with LGBT that there's no common Chinese translation? Then it's fine. It's kind of like how the word \"OK\" has been absorbed into daily spoken Chinese I guess. Â 08:14 2004年5月31日 (UTC)", @@ -87,7 +87,7 @@ export const example_conversation2: conversation.Conversation = { "content": "== Name == ", "cleaned_content": "== Name == ", "indentation": 0, - "comment_type": "MODIFICATION", + "type": "MODIFICATION", "user_text": "Adamdaley", "timestamp": "2013-04-16 08:55:31 UTC", "replyTo_id": "", @@ -99,7 +99,7 @@ export const example_conversation2: conversation.Conversation = { "content": " I edited it to the largest \"labor\" uprising and the largest \"organized armed uprising\" since the civil war. They were not in rebellion per se and the race riots of the 60's are clearly a larger uprising (I'm not too sure on armed).", "cleaned_content": " I edited it to the largest \"labor\" uprising and the largest \"organized armed uprising\" since the civil war. They were not in rebellion per se and the race riots of the 60's are clearly a larger uprising (I'm not too sure on armed).", "indentation": 0, - "comment_type": "ADDITION", + "type": "ADDITION", "user_text": "70.151.72.162", "timestamp": "2015-08-07 17:03:18 UTC", "replyTo_id": "550613551.0.0", @@ -111,7 +111,7 @@ export const example_conversation2: conversation.Conversation = { "content": "Edited it to say \"one of the largest, organized, and well-armed uprisings.\" This seemed like the best course of action. It may be settling but hopefully it resolves the dispute. \u2014\u00a0Preceding [WIKI_LINK: Wikipedia:Signatures@unsigned] comment added by ", "cleaned_content": "Edited it to say \"one of the largest, organized, and well-armed uprisings.\" This seemed like the best course of action. It may be settling but hopefully it resolves the dispute. \u2014\u00a0Preceding [WIKI_LINK: Wikipedia:Signatures@unsigned] comment added by ", "indentation": 0, - "comment_type": "MODIFICATION", + "type": "MODIFICATION", "user_text": "SineBot", "timestamp": "2015-08-07 17:03:18 UTC", "replyTo_id": "675014505.416.416", @@ -127,7 +127,7 @@ export const example_conversation3: conversation.Conversation = { "content": "foo comment 1 text", "cleaned_content": "foo comment 1 text", "indentation": 0, - "comment_type": "ADDITION", + "type": "ADDITION", "user_text": "70.151.72.162", "timestamp": "2015-08-07 17:03:18 UTC", "replyTo_id": null, @@ -135,7 +135,7 @@ export const example_conversation3: conversation.Conversation = { "page_title": "talk page title", }, "675014505.20.416": { - "comment_type": "ADDITION", + "type": "ADDITION", "content": "foo comment 2 text", "cleaned_content": "foo comment 2 text", "indentation": 0, @@ -150,7 +150,7 @@ export const example_conversation3: conversation.Conversation = { export const example_conversation4: conversation.Conversation = { "159766698.24.0": { - "comment_type": "ADDITION", + "type": "ADDITION", "content": "\nYour recent edit to [WIKI_LINK: Barton on Sea] ([EXTERNAL_LINK: diff]) was reverted by an automated bot. The edit was identified as adding either test edits, [WIKI_LINK: WP:VAND@vandalism], or [WIKI_LINK: WP:SPAM@link spam] to the page or having an inappropriate [WIKI_LINK: WP:ES@edit summary]. If you want to experiment, please use the preview button while editing or consider using the [WIKI_LINK: Wikipedia:Sandbox@sandbox]. If this revert was in error, please contact the bot operator. If you made an edit that removed a large amount of content, try doing smaller edits instead. Thanks! //", "cleaned_content": "\nYour recent edit to [WIKI_LINK: Barton on Sea] ([EXTERNAL_LINK: diff]) was reverted by an automated bot. The edit was identified as adding either test edits, [WIKI_LINK: WP:VAND@vandalism], or [WIKI_LINK: WP:SPAM@link spam] to the page or having an inappropriate [WIKI_LINK: WP:ES@edit summary]. If you want to experiment, please use the preview button while editing or consider using the [WIKI_LINK: Wikipedia:Sandbox@sandbox]. If this revert was in error, please contact the bot operator. If you made an edit that removed a large amount of content, try doing smaller edits instead. Thanks! //", "indentation": 0, @@ -162,7 +162,7 @@ export const example_conversation4: conversation.Conversation = { "user_text": "VoABot II", }, "159766698.0.0": { - "comment_type": "CREATION", + "type": "CREATION", "content": "\n==Regarding your edits to [WIKI_LINK: Barton on Sea]:==", "cleaned_content": "\n==Regarding your edits to [WIKI_LINK: Barton on Sea]:==", "indentation": 0, @@ -181,7 +181,7 @@ export const example_conversation5: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": ": Actually, based on my research, I did not find any unifid traslation. All the articles used the english term directly followed a parenthetical explaination. it's not convinent to use a chinese traslation, because it needs around 20 chinese characters to define the meaning of LGBT. I thought it is forbidden here using an english expression as title of a item. So, thanks your response. -- [[User:vegie|vegie]] 19:44 2004年5月31日\n", - "comment_type": "ADDITION", + "type": "ADDITION", "timestamp": "2004-05-31T10:44:26.000Z", "parent_id": null, "cleaned_content": " Actually, based on my research, I did not find any unifid traslation. All the articles used the english term directly followed a parenthetical explaination. it's not convinent to use a chinese traslation, because it needs around 20 chinese characters to define the meaning of LGBT. I thought it is forbidden here using an english expression as title of a item. So, thanks your response. 19:44 2004年5月31日", @@ -194,7 +194,7 @@ export const example_conversation5: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Sunzx", "content": ":: 用英文题目应该没问题的. 不过最好在文章里面说明\"此条目名称尚未有统一的中文翻译\"之类的... --[[User:Sunzx|Sunzx]] ([[User_talk:Sunzx|Talk]])[[北京大学| ]] 10:46 2004年5月31日 (UTC)\n", - "comment_type": "ADDITION", + "type": "ADDITION", "timestamp": "2004-05-31T10:46:48.000Z", "parent_id": null, "cleaned_content": " 用英文题目应该没问题的. 不过最好在文章里面说明\"此条目名称尚未有统一的中文翻译\"之类的... 10:46 2004年5月31日 (UTC)", @@ -207,7 +207,7 @@ export const example_conversation5: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": "==Translation Problem==\n", - "comment_type": "CREATION", + "type": "CREATION", "timestamp": "2004-05-31T06:03:04.000Z", "parent_id": null, "cleaned_content": "Translation Problem", @@ -220,7 +220,7 @@ export const example_conversation5: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". --[[User:Wing|Wing]] 15:02 2004年31日\n", - "comment_type": "DELETION", + "type": "DELETION", "timestamp": "2004-05-31T06:25:49.000Z", "parent_id": "99858.17811.17782", "cleaned_content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". 15:02 2004年31日", @@ -233,7 +233,7 @@ export const example_conversation5: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". --[[User:Wing|Wing]] 15:02 2004年31日\n", - "comment_type": "ADDITION", + "type": "ADDITION", "timestamp": "2004-05-31T06:03:04.000Z", "parent_id": null, "cleaned_content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". 15:02 2004年31日", @@ -246,7 +246,7 @@ export const example_conversation5: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Menchi", "content": ": If Chinese speakers didn't bother to invent a translate and most Chinese decided to just use the English term, then we can too. It sounds like it's the case with LGBT -- that there's no common Chinese translation? Then it's fine. It's kind of like how the word \"OK\" has been absorbed into daily spoken Chinese I guess. --[[User:Menchi|Menchi]] ([[User talk:Menchi|討論頁]])[[前247年|Â]] 08:14 2004年5月31日 (UTC)\n", - "comment_type": "ADDITION", + "type": "ADDITION", "timestamp": "2004-05-31T08:14:37.000Z", "parent_id": null, "cleaned_content": " If Chinese speakers didn't bother to invent a translate and most Chinese decided to just use the English term, then we can too. It sounds like it's the case with LGBT that there's no common Chinese translation? Then it's fine. It's kind of like how the word \"OK\" has been absorbed into daily spoken Chinese I guess. Â 08:14 2004年5月31日 (UTC)", @@ -262,7 +262,7 @@ export const example_conversation6: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": "==Translation Problem==\n", - "comment_type": "CREATION", + "type": "CREATION", "timestamp": "2004-05-31T06:03:04.000Z", "parent_id": null, "cleaned_content": "Translation Problem", @@ -275,7 +275,7 @@ export const example_conversation6: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". --[[User:Wing|Wing]] 15:02 2004年31日\n", - "comment_type": "DELETION", + "type": "DELETION", "timestamp": "2004-05-31T06:25:49.000Z", "parent_id": "99858.17811.17782", "cleaned_content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". 15:02 2004年31日", @@ -288,7 +288,7 @@ export const example_conversation6: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". --[[User:Wing|Wing]] 15:02 2004年31日\n", - "comment_type": "ADDITION", + "type": "ADDITION", "timestamp": "2004-05-31T06:03:04.000Z", "parent_id": null, "cleaned_content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". 15:02 2004年31日", @@ -301,7 +301,7 @@ export const example_conversation6: conversation.Conversation = { "page_title": "User talk:Menchi/貯藏室二", "user_text": "Vegie", "content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". --[[User:Wing|Wing]] 15:02 2004年31日\n", - "comment_type": "RESTORATION", + "type": "RESTORATION", "timestamp": "2004-05-31T07:04:04.000Z", "parent_id": "99858.17811.17782", "cleaned_content": "what if I could not find a proper expression for a specail english word, which is using by some chinese website, can i use the english word as title of item? for example, abbreviation of \"LGBT\". 15:02 2004年31日", diff --git a/view_conversations/conv-viewer-webapi/README.md b/view_conversations/conv-viewer-webapi/README.md index f396b9f1..7c02199d 100644 --- a/view_conversations/conv-viewer-webapi/README.md +++ b/view_conversations/conv-viewer-webapi/README.md @@ -45,9 +45,9 @@ Before you can deploy, you need to: 1. Copy the `server_config.template.json` file to `build/config/server_config.json`. 2. In the `build/config/server_config.json` file, set these values: - * `bigQueryProjectId` : The Google Cloud Project ID that contains the BigQuery database. - * `bigQueryDataSetId` : The name of the dataset in the cloud project. - * `bigQueryTable` : The name of the table that contains the conversations. + * `spannerProjectId` : The Google Cloud Project ID that contains the Spanner database. + * `spannerDataSetId` : The name of the dataset in the cloud project. + * `spannerTable` : The name of the table that contains the conversations. TODO(ldixon): in future we'll move to using OAuth and project credentials. diff --git a/view_conversations/conv-viewer-webapi/server_config.template.json b/view_conversations/conv-viewer-webapi/server_config.template.json index 9a6e7a9a..77e3de3d 100644 --- a/view_conversations/conv-viewer-webapi/server_config.template.json +++ b/view_conversations/conv-viewer-webapi/server_config.template.json @@ -3,8 +3,8 @@ "isProduction": false, "staticPath": "build/static", - "cloudProjectId": "wikidetox-viz", - "spannerInstanceId": "wikiconv", - "spannerDatabaseName": "convdata", - "spannerTableName": "zh_20180601_conv" + "cloudProjectId": "yourCloudProject", + "spannerInstanceId": "yourSpannerInstance", + "spannerDatabaseName": "yourSpannerDatabase", + "spannerTableName": "yourSpannerTable" } diff --git a/view_conversations/conv-viewer-webapi/src/db_types.ts b/view_conversations/conv-viewer-webapi/src/db_types.ts index 061edf63..7467ac0e 100644 --- a/view_conversations/conv-viewer-webapi/src/db_types.ts +++ b/view_conversations/conv-viewer-webapi/src/db_types.ts @@ -146,6 +146,7 @@ const handlers : Array> = new StringFieldHandler('type'), new IntFieldHandler('user_id'), new StringFieldHandler('user_text'), + new FloatFieldHandler('Score'), ]; function addHandler(inputHandlers : HandlerSet, handler : SpannerFieldHandler) @@ -154,17 +155,19 @@ function addHandler(inputHandlers : HandlerSet, handler : SpannerFieldHandler(addHandler, {}); +const ScoreSubstrings = 'RockV6_1|Smirnoff_2'; export function parseOutputRows(rows: spanner.ResultRow[]) : T[] { const output : ParsedOutput[] = [] for (const row of rows) { const ret: { [fieldName:string] : string | string[] | Date | number | null } = {}; for (const field of row) { - if(!(field.name in handlerSet)) { + const testname = new RegExp(ScoreSubstrings).test(field.name) ? 'Score' : field.name; + if(!(testname in handlerSet)) { console.error(`Field ${field.name} does not have a handler and so cannot be interpreted.`); break; } - ret[field.name] = handlerSet[field.name].fromSpannerResultField(field.value); + ret[field.name] = handlerSet[testname].fromSpannerResultField(field.value); } output.push(ret) } diff --git a/view_conversations/conv-viewer-webapi/src/routes.ts b/view_conversations/conv-viewer-webapi/src/routes.ts index 6768cb69..593caba5 100644 --- a/view_conversations/conv-viewer-webapi/src/routes.ts +++ b/view_conversations/conv-viewer-webapi/src/routes.ts @@ -45,10 +45,11 @@ export function setup( try { let conv_id: runtime_types.ConversationId = runtime_types.ConversationId.assert(req.params.conv_id); + const index = conf.spannerTableName + '_by_conversation_id'; // TODO remove outer try wrapper unless it get used. const sqlQuery = `SELECT * - FROM ${table} + FROM ${table}@{FORCE_INDEX=${index}} WHERE conversation_id="${conv_id}" LIMIT 100`; // Query options list: @@ -73,19 +74,17 @@ export function setup( try { let comment_id: runtime_types.CommentId = runtime_types.CommentId.assert(req.params.comment_id); + const index = conf.spannerTableName + '_by_conversation_id'; + // TODO remove outer try wrapper unless it get used. // id field is unique. const sqlQuery = ` - SELECT r.* - FROM ${table} as r - JOIN - (SELECT conversation_id, timestamp - FROM ${table} - WHERE id="${comment_id}") as l - ON r.conversation_id=l.conversation_id - WHERE r.timestamp <= l.timestamp - `; + SELECT conv_r.* + FROM ${table} conv_l + JOIN ${table}@{FORCE_INDEX=${index}} conv_r + ON conv_r.conversation_id = conv_l.conversation_id + WHERE conv_l.id = "${comment_id}" and conv_r.timestamp <= conv_l.timestamp`; // Query options list: // https://cloud.google.com/spanner/docs/getting-started/nodejs/#query_data_using_sql const query: spanner.Query = { From a7fe0a3dd184147f52aee15f56bb2c0897eea76a Mon Sep 17 00:00:00 2001 From: Yiqing Hua Date: Fri, 27 Jul 2018 21:47:12 +0000 Subject: [PATCH 4/9] being able to show list of comments now --- .../conv-viewer-webapi/src/routes.ts | 31 +++++++ .../src/app/app.component.html | 43 +++++++++- .../src/app/app.component.ts | 77 +++++++++++++++-- .../conv-viewer-webapp/src/app/app.module.ts | 2 + .../src/app/comment/comment.component.css | 82 +++++++++++++++++++ .../src/app/comment/comment.component.html | 12 +++ .../src/app/comment/comment.component.spec.ts | 25 ++++++ .../src/app/comment/comment.component.ts | 19 +++++ .../conversation/conversation.component.css | 2 +- 9 files changed, 280 insertions(+), 13 deletions(-) create mode 100644 view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css create mode 100644 view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html create mode 100644 view_conversations/conv-viewer-webapp/src/app/comment/comment.component.spec.ts create mode 100644 view_conversations/conv-viewer-webapp/src/app/comment/comment.component.ts diff --git a/view_conversations/conv-viewer-webapi/src/routes.ts b/view_conversations/conv-viewer-webapi/src/routes.ts index 593caba5..73f5b48e 100644 --- a/view_conversations/conv-viewer-webapi/src/routes.ts +++ b/view_conversations/conv-viewer-webapi/src/routes.ts @@ -70,6 +70,37 @@ export function setup( } }); + app.get('/api/toxicity/:score', async (req, res) => { + try { + let score: number = req.params.score; + const index = conf.spannerTableName + '_by_toxicity'; + + // TODO remove outer try wrapper unless it get used. + const sqlQuery = `SELECT * + FROM ${table}@{FORCE_INDEX=${index}} + WHERE RockV6_1_TOXICITY <= ${score} and type != "DELETION" + ORDER BY RockV6_1_TOXICITY DESC + LIMIT 20`; + // Query options list: + // https://cloud.google.com/spanner/docs/getting-started/nodejs/#query_data_using_sql + const query: spanner.Query = { + sql: sqlQuery + }; + + await spannerDatabase.run(query).then(results => { + const rows = results[0]; + res.status(httpcodes.OK).send(JSON.stringify(db_types.parseOutputRows(rows), null, 2)); + }); + } catch (e) { + console.error(`*** Failed: `, e); + res.status(httpcodes.INTERNAL_SERVER_ERROR).send(JSON.stringify({ + error: e.message + })); + } + }); + + + app.get('/api/comment-id/:comment_id', async (req, res) => { try { let comment_id: runtime_types.CommentId = diff --git a/view_conversations/conv-viewer-webapp/src/app/app.component.html b/view_conversations/conv-viewer-webapp/src/app/app.component.html index 40a108a5..62dd6bd0 100644 --- a/view_conversations/conv-viewer-webapp/src/app/app.component.html +++ b/view_conversations/conv-viewer-webapp/src/app/app.component.html @@ -4,8 +4,38 @@

WikiDetox Conversation Viewer

+
-
+ + + + + + {{s}} + + + + + + + + +
+ + +
+ + +
+
@@ -45,4 +75,11 @@

+
+
+ +
+
+ +
{{searchResult}}
diff --git a/view_conversations/conv-viewer-webapp/src/app/app.component.ts b/view_conversations/conv-viewer-webapp/src/app/app.component.ts index 54485429..52a9da23 100644 --- a/view_conversations/conv-viewer-webapp/src/app/app.component.ts +++ b/view_conversations/conv-viewer-webapp/src/app/app.component.ts @@ -12,16 +12,23 @@ const REVISION_ID_TEXT = 'Revision ID'; const PAGE_ID_TEXT = 'Page ID'; const PAGE_TITLE_TEXT = 'Page Name'; +const MOST_TOXIC_TEXT = 'Most Toxic'; + const URL_PART_FOR_SEARCHBY: {[text: string]: string} = {}; +const URL_PART_FOR_BROWSEBY: {[text: string]: string} = {}; URL_PART_FOR_SEARCHBY[COMMENT_ID_TEXT] = 'comment-id'; URL_PART_FOR_SEARCHBY[CONVERSATION_ID_TEXT] = 'conversation-id'; URL_PART_FOR_SEARCHBY[REVISION_ID_TEXT] = 'revision-id'; URL_PART_FOR_SEARCHBY[PAGE_TITLE_TEXT] = 'page-title'; URL_PART_FOR_SEARCHBY[PAGE_ID_TEXT] = 'page-id'; +URL_PART_FOR_BROWSEBY[MOST_TOXIC_TEXT] = 'toxicity'; + interface HashObj { - searchBy: string; - searchFor: string; + searchBy?: string; + searchFor?: string; + browseBy?: string; + browseFor?: string; embed: boolean; showPageContext: boolean; highlightId?: string; @@ -33,23 +40,33 @@ interface HashObj { styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { + browseBys = [MOST_TOXIC_TEXT]; searchBys = [ CONVERSATION_ID_TEXT, COMMENT_ID_TEXT, REVISION_ID_TEXT, PAGE_ID_TEXT, PAGE_TITLE_TEXT ]; inFlightRequest?: Subscription; + inFlightBrowseRequest?: Subscription; rootComment?: wpconvlib.Comment; - form: FormGroup; + answerComments?: wpconvlib.Comment[]; + searchForm: FormGroup; + browseForm: FormGroup; + embed = false; showPageContext = true; highlightId: string; searchResult = ''; + browseResult = ''; errorMessage?: string = null; constructor(private http: HttpClient, private formBuilder: FormBuilder) { let searchBy = CONVERSATION_ID_TEXT; let searchFor = ''; + + let browseBy = MOST_TOXIC_TEXT; + let browseFor = 1; + console.log(`init-hash: ${document.location.hash}`); try { const hashObj: HashObj = JSON.parse(document.location.hash.substr(1)); @@ -68,14 +85,23 @@ export class AppComponent implements OnInit { } } - this.form = formBuilder.group({ + this.searchForm = formBuilder.group({ searchBy: new FormControl(searchBy, Validators.required), searchFor: new FormControl(searchFor, Validators.required), }); + this.browseForm = formBuilder.group({ + browseBy: new FormControl(browseBy, Validators.required), + browseFor: new FormControl(browseFor, Validators.required), + }); + if (searchFor && searchBy && this.embed) { this.submitSearch(); } + if (browseFor && browseBy && this.embed) { + this.submitBrowse(); + } + } ngOnInit(): void {} @@ -83,8 +109,10 @@ export class AppComponent implements OnInit { updateLocationHash() { console.log('updateLocationHash'); const objToEncode: HashObj = { - searchBy: this.form.value.searchBy, - searchFor: this.form.value.searchFor, + searchBy: this.searchForm.value.searchBy, + searchFor: this.searchForm.value.searchFor, + browseBy: this.browseForm.value.browseBy, + browseFor: this.browseForm.value.browseFor, embed: this.embed, showPageContext: this.showPageContext, }; @@ -96,15 +124,15 @@ export class AppComponent implements OnInit { submitSearch() { console.log('model-based form submitted'); - console.log(this.form.value); + console.log(this.searchForm.value); this.errorMessage = null; this.updateLocationHash(); this.inFlightRequest = this.http .get(encodeURI( - '/api/' + URL_PART_FOR_SEARCHBY[this.form.value.searchBy] + - '/' + this.form.value.searchFor)) + '/api/' + URL_PART_FOR_SEARCHBY[this.searchForm.value.searchBy] + + '/' + this.searchForm.value.searchFor)) .subscribe( (actions: wpconvlib.Comment[]) => { console.log('got conversation!'); @@ -142,4 +170,35 @@ export class AppComponent implements OnInit { delete this.inFlightRequest; }); } + + submitBrowse() { + console.log('model-based browse form submitted'); + console.log(this.browseForm.value); + this.errorMessage = null; + this.updateLocationHash(); + + this.inFlightBrowseRequest = + this.http + .get(encodeURI( + '/api/' + URL_PART_FOR_BROWSEBY[this.browseForm.value.browseBy] + + '/' + this.browseForm.value.browseFor)) + .subscribe( + (comments: wpconvlib.Comment[]) => { + console.log('got comments!'); + this.browseResult = JSON.stringify(comments, null, 2); + delete this.inFlightBrowseRequest; + console.log(comments); + this.answerComments = comments; + }, + (e) => { + console.log(e); + if (e.error && e.error.error) { + this.errorMessage = e.message + '\n' + e.error.error; + } else { + this.errorMessage = e.message; + } + delete this.inFlightBrowseRequest; + }); + } + } diff --git a/view_conversations/conv-viewer-webapp/src/app/app.module.ts b/view_conversations/conv-viewer-webapp/src/app/app.module.ts index d288789c..aa12bfec 100755 --- a/view_conversations/conv-viewer-webapp/src/app/app.module.ts +++ b/view_conversations/conv-viewer-webapp/src/app/app.module.ts @@ -40,6 +40,7 @@ import {HttpClientModule} from '@angular/common/http'; import { AppComponent } from './app.component'; import { ConversationComponent } from './conversation/conversation.component'; +import { CommentComponent } from './comment/comment.component'; @NgModule({ @@ -84,6 +85,7 @@ import { ConversationComponent } from './conversation/conversation.component'; declarations: [ AppComponent, ConversationComponent, + CommentComponent, ], providers: [], bootstrap: [AppComponent] diff --git a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css new file mode 100644 index 00000000..bab23562 --- /dev/null +++ b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css @@ -0,0 +1,82 @@ +.debug-pre-output { + font-family: monospace; + white-space: pre; + border: 1px #AAA solid; +} + +.indent { + margin-left: 1em; +} + +hr { + display: block; + height: 1px; +} + +last { + color: blue; +} + +.hidden { + display: none; +} + +.action { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: flex-start; +} + +.comment { + padding: 5px; + margin: 2px; + margin-left: 10px; + flex: auto; +} + +.index { + border: 1px solid #CCC; + padding: 5px; + width: 2em; + border-radius: 5px; + text-align: center; +} + +.finalcomment { + border: 2px dotted #88F; +} + +.content{ + padding: 5px; + background: #FFF; + width: 100%; + /* border: 1px solid #CCC; */ +} + +/* TODO: figure out how to make vertical line that + scales with the heigh of the containing element */ +.sep { +} + +.whenandwho { + width: 100px; + text-align: right; + font-size: 80%; + flex: none; + display: block; + + /* padding-right: 5px; + border-right: 2px solid #CCC; */ + margin-right: 5px; +} + +.author { + color: #44B; + font-weight: bold; +} + +.timestamp { + font-size: 80%; + color: #BBB; +} diff --git a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html new file mode 100644 index 00000000..d049f968 --- /dev/null +++ b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html @@ -0,0 +1,12 @@ +
+ + + + + +
+
{{comment.user_text}}
+
{{comment.timestamp}}
+
{{comment.cleaned_content}}
+
+ diff --git a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.spec.ts b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.spec.ts new file mode 100644 index 00000000..bcac3f25 --- /dev/null +++ b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CommentComponent } from './comment.component'; + +describe('CommentComponent', () => { + let component: CommentComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CommentComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.ts b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.ts new file mode 100644 index 00000000..48a570e7 --- /dev/null +++ b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.ts @@ -0,0 +1,19 @@ +import { Component, Input, OnInit } from '@angular/core'; +import * as wpconvlib from '@conversationai/wpconvlib'; + +@Component({ + selector: 'app-comment', + templateUrl: './comment.component.html', + styleUrls: ['./comment.component.css'] +}) +export class CommentComponent implements OnInit { + @Input() comment: wpconvlib.Comment; + dbg: string; + + constructor() { } + + ngOnInit() { + this.dbg = JSON.stringify(this.comment, null, 2); + } + +} diff --git a/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.css b/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.css index cb2b4230..bab23562 100644 --- a/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.css +++ b/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.css @@ -79,4 +79,4 @@ last { .timestamp { font-size: 80%; color: #BBB; -} \ No newline at end of file +} From 1907dd7c249a96841f22ea7762d6eb0d747d213c Mon Sep 17 00:00:00 2001 From: Yiqing Hua Date: Mon, 30 Jul 2018 18:37:57 +0000 Subject: [PATCH 5/9] comment collapsed into conversations --- crowdsourcing/wpconvlib/src/conversation.ts | 7 +- .../src/app/app.component.css | 105 +++++++++++++++++- .../src/app/app.component.html | 31 +++++- .../src/app/app.component.ts | 51 +++++++++ .../conv-viewer-webapp/src/app/comment/\\" | 97 ++++++++++++++++ .../src/app/comment/comment.component.css | 15 +++ .../src/app/comment/comment.component.html | 15 ++- 7 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 "view_conversations/conv-viewer-webapp/src/app/comment/\\" diff --git a/crowdsourcing/wpconvlib/src/conversation.ts b/crowdsourcing/wpconvlib/src/conversation.ts index ea329d94..d4625c2e 100644 --- a/crowdsourcing/wpconvlib/src/conversation.ts +++ b/crowdsourcing/wpconvlib/src/conversation.ts @@ -38,7 +38,7 @@ export interface Comment { // the current snapshot. latestVersion?: string | null; // id of the latest version of this comment if isPresent // is false, otherwise id of self. - dfs_index?: number // index according to Depth First Search of conv. + dfs_index?: number; // index according to Depth First Search of conv. // Starting here are toxicity scores, since they don't exist in all datasets // except in English, this fields are optional. RockV6_1_FLIRTATION?: number | null, @@ -64,6 +64,9 @@ export interface Comment { Smirnoff_2_OFF_TOPIC?: number | null, Smirnoff_2_SPAM?: number | null, Smirnoff_2_UNSUBSTANTIAL?: number | null, + // The following fields are used in displaying comments in viz app. + isCollapsed?: boolean, + rootComment?: Comment, } export interface Conversation { [id: string]: Comment } @@ -195,7 +198,7 @@ export function htmlForComment( export function walkDfsComments( rootComment: Comment, f: (c: Comment) => void): void { const commentsHtml = []; - let nextComment: Comment|undefined = rootComment; + const nextComment: Comment|undefined = rootComment; if (nextComment) { f(nextComment); if (nextComment.children) { diff --git a/view_conversations/conv-viewer-webapp/src/app/app.component.css b/view_conversations/conv-viewer-webapp/src/app/app.component.css index 4a257c7f..0859644e 100644 --- a/view_conversations/conv-viewer-webapp/src/app/app.component.css +++ b/view_conversations/conv-viewer-webapp/src/app/app.component.css @@ -32,4 +32,107 @@ .convid-text { font-size: 80%; color: #BBB; -} \ No newline at end of file +} + +.debug-pre-output { + font-family: monospace; + white-space: pre; + border: 1px #AAA solid; +} + +.indent { + margin-left: 1em; +} + +hr { + display: block; + height: 1px; +} + +last { + color: blue; +} + +.hidden { + display: none; +} + +.action { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: flex-start; +} + +.comment { + padding: 5px; + margin: 2px; + margin-left: 10px; + flex: auto; +} + +.index { + border: 1px solid #CCC; + padding: 5px; + width: 2em; + border-radius: 5px; + text-align: center; +} + +.finalcomment { + border: 2px dotted #88F; +} + +.content{ + padding: 5px; + background: #FFF; + width: 100%; + /* border: 1px solid #CCC; */ +} + +/* TODO: figure out how to make vertical line that + scales with the heigh of the containing element */ +.sep { +} + +.whenandwho { + width: 100px; + text-align: right; + font-size: 80%; + flex: none; + display: block; + + /* padding-right: 5px; + border-right: 2px solid #CCC; */ + margin-right: 5px; +} + +.author { + color: #44B; + font-weight: bold; +} + +.timestamp { + font-size: 80%; + color: #BBB; +} + +.collapseComment{ + border: 1px solid #CCC; + padding: 5px; + background: #EEE; +} + +.collapsible-action { + background-color: Grey; + color: #444; + cursor: pointer; + padding: 18px; + width: 100%; + border: solid lightGrey; + text-align: left; + outline: none; + font-size: 15px; +} + + diff --git a/view_conversations/conv-viewer-webapp/src/app/app.component.html b/view_conversations/conv-viewer-webapp/src/app/app.component.html index 62dd6bd0..d8d1889b 100644 --- a/view_conversations/conv-viewer-webapp/src/app/app.component.html +++ b/view_conversations/conv-viewer-webapp/src/app/app.component.html @@ -37,6 +37,7 @@

+ @@ -77,7 +78,35 @@

- + + + +
+
{{errorMessage}}
+
+
+
In page: {{comment.rootComment.page_title}}
+
+ (conversation id: {{comment.rootComment.id}}) +
+
+ + +
+
+ + +
diff --git a/view_conversations/conv-viewer-webapp/src/app/app.component.ts b/view_conversations/conv-viewer-webapp/src/app/app.component.ts index 52a9da23..f4ad07d9 100644 --- a/view_conversations/conv-viewer-webapp/src/app/app.component.ts +++ b/view_conversations/conv-viewer-webapp/src/app/app.component.ts @@ -122,6 +122,54 @@ export class AppComponent implements OnInit { document.location.hash = JSON.stringify(objToEncode); } + submitCommentSearch(comment : wpconvlib.Comment) { + console.log('model-based form submitted'); + this.errorMessage = null; + this.updateLocationHash(); + + this.inFlightRequest = + this.http + .get(encodeURI( + '/api/comment-id/' + comment.id)) + .subscribe( + (actions: wpconvlib.Comment[]) => { + console.log('got conversation!'); + this.searchResult = JSON.stringify(actions, null, 2); + delete this.inFlightRequest; + + const conversation: wpconvlib.Conversation = {}; + for (const a of actions) { + conversation[a.id] = a; + if (this.highlightId) { + if (this.highlightId === a.id) { + conversation[a.id].comment_to_highlight = a.id; + } else { + delete conversation[a.id].comment_to_highlight; + } + } + } + + console.log(conversation); + + comment.rootComment = + wpconvlib.structureConversaton(conversation); + if (!comment.rootComment) { + this.errorMessage = 'No Root comment in conversation'; + return; + } + }, + (e) => { + console.log(e); + if (e.error && e.error.error) { + this.errorMessage = e.message + '\n' + e.error.error; + } else { + this.errorMessage = e.message; + } + delete this.inFlightRequest; + }); + } + + submitSearch() { console.log('model-based form submitted'); console.log(this.searchForm.value); @@ -188,6 +236,9 @@ export class AppComponent implements OnInit { this.browseResult = JSON.stringify(comments, null, 2); delete this.inFlightBrowseRequest; console.log(comments); + for (const comment of comments) { + comment.isCollapsed = false; + } this.answerComments = comments; }, (e) => { diff --git "a/view_conversations/conv-viewer-webapp/src/app/comment/\\" "b/view_conversations/conv-viewer-webapp/src/app/comment/\\" new file mode 100644 index 00000000..4b7aa6ba --- /dev/null +++ "b/view_conversations/conv-viewer-webapp/src/app/comment/\\" @@ -0,0 +1,97 @@ +.debug-pre-output { + font-family: monospace; + white-space: pre; + border: 1px #AAA solid; +} + +.indent { + margin-left: 1em; +} + +hr { + display: block; + height: 1px; +} + +last { + color: blue; +} + +.hidden { + display: none; +} + +.action { + display: flex; + background-color: lightgrey; + flex-direction: row; + flex-wrap: nowrap; + align-items: flex-start; +} + +.collapsible-action { + background-color: #eee; + color: #444; + cursor: pointer; + padding: 18px; + width: 100%; + border: none; + text-align: left; + outline: none; + font-size: 15px; +} + + + +.comment { + padding: 5px; + margin: 2px; + margin-left: 10px; + flex: auto; +} + +.index { + border: 1px solid #CCC; + padding: 5px; + width: 2em; + border-radius: 5px; + text-align: center; +} + +.finalcomment { + border: 2px dotted #88F; +} + +.content{ + padding: 5px; + background: #FFF; + width: 100%; + /* border: 1px solid #CCC; */ +} + +/* TODO: figure out how to make vertical line that + scales with the heigh of the containing element */ +.sep { +} + +.whenandwho { + width: 100px; + text-align: right; + font-size: 80%; + flex: none; + display: block; + + /* padding-right: 5px; + border-right: 2px solid #CCC; */ + margin-right: 5px; +} + +.author { + color: #44B; + font-weight: bold; +} + +.timestamp { + font-size: 80%; + color: #BBB; +} diff --git a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css index bab23562..af8eee68 100644 --- a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css +++ b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css @@ -23,11 +23,26 @@ last { .action { display: flex; + background-color: #eee; flex-direction: row; flex-wrap: nowrap; align-items: flex-start; } +.collapsible-action { + background-color: #eee; + color: #444; + cursor: pointer; + padding: 18px; + width: 100%; + border: none; + text-align: left; + outline: none; + font-size: 15px; +} + + + .comment { padding: 5px; margin: 2px; diff --git a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html index d049f968..773e86c0 100644 --- a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html +++ b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html @@ -1,4 +1,4 @@ -
+
+ + +
{{errorMessage}}
+
+
+
In page: {{rootComment.page_title}}
+
+ (conversation id: {{rootComment.id}}) +
+
+ +
From 86c1d4f7a2e72425ca5e5a8319e796e76cb26e06 Mon Sep 17 00:00:00 2001 From: Yiqing Hua Date: Mon, 30 Jul 2018 18:38:17 +0000 Subject: [PATCH 6/9] comment collapsed into conversations --- .../conv-viewer-webapp/src/app/comment/\\" | 97 ------------------- .../src/app/comment/comment.component.css | 97 ------------------- .../src/app/comment/comment.component.html | 23 ----- .../src/app/comment/comment.component.spec.ts | 25 ----- .../src/app/comment/comment.component.ts | 19 ---- 5 files changed, 261 deletions(-) delete mode 100644 "view_conversations/conv-viewer-webapp/src/app/comment/\\" delete mode 100644 view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css delete mode 100644 view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html delete mode 100644 view_conversations/conv-viewer-webapp/src/app/comment/comment.component.spec.ts delete mode 100644 view_conversations/conv-viewer-webapp/src/app/comment/comment.component.ts diff --git "a/view_conversations/conv-viewer-webapp/src/app/comment/\\" "b/view_conversations/conv-viewer-webapp/src/app/comment/\\" deleted file mode 100644 index 4b7aa6ba..00000000 --- "a/view_conversations/conv-viewer-webapp/src/app/comment/\\" +++ /dev/null @@ -1,97 +0,0 @@ -.debug-pre-output { - font-family: monospace; - white-space: pre; - border: 1px #AAA solid; -} - -.indent { - margin-left: 1em; -} - -hr { - display: block; - height: 1px; -} - -last { - color: blue; -} - -.hidden { - display: none; -} - -.action { - display: flex; - background-color: lightgrey; - flex-direction: row; - flex-wrap: nowrap; - align-items: flex-start; -} - -.collapsible-action { - background-color: #eee; - color: #444; - cursor: pointer; - padding: 18px; - width: 100%; - border: none; - text-align: left; - outline: none; - font-size: 15px; -} - - - -.comment { - padding: 5px; - margin: 2px; - margin-left: 10px; - flex: auto; -} - -.index { - border: 1px solid #CCC; - padding: 5px; - width: 2em; - border-radius: 5px; - text-align: center; -} - -.finalcomment { - border: 2px dotted #88F; -} - -.content{ - padding: 5px; - background: #FFF; - width: 100%; - /* border: 1px solid #CCC; */ -} - -/* TODO: figure out how to make vertical line that - scales with the heigh of the containing element */ -.sep { -} - -.whenandwho { - width: 100px; - text-align: right; - font-size: 80%; - flex: none; - display: block; - - /* padding-right: 5px; - border-right: 2px solid #CCC; */ - margin-right: 5px; -} - -.author { - color: #44B; - font-weight: bold; -} - -.timestamp { - font-size: 80%; - color: #BBB; -} diff --git a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css deleted file mode 100644 index af8eee68..00000000 --- a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.css +++ /dev/null @@ -1,97 +0,0 @@ -.debug-pre-output { - font-family: monospace; - white-space: pre; - border: 1px #AAA solid; -} - -.indent { - margin-left: 1em; -} - -hr { - display: block; - height: 1px; -} - -last { - color: blue; -} - -.hidden { - display: none; -} - -.action { - display: flex; - background-color: #eee; - flex-direction: row; - flex-wrap: nowrap; - align-items: flex-start; -} - -.collapsible-action { - background-color: #eee; - color: #444; - cursor: pointer; - padding: 18px; - width: 100%; - border: none; - text-align: left; - outline: none; - font-size: 15px; -} - - - -.comment { - padding: 5px; - margin: 2px; - margin-left: 10px; - flex: auto; -} - -.index { - border: 1px solid #CCC; - padding: 5px; - width: 2em; - border-radius: 5px; - text-align: center; -} - -.finalcomment { - border: 2px dotted #88F; -} - -.content{ - padding: 5px; - background: #FFF; - width: 100%; - /* border: 1px solid #CCC; */ -} - -/* TODO: figure out how to make vertical line that - scales with the heigh of the containing element */ -.sep { -} - -.whenandwho { - width: 100px; - text-align: right; - font-size: 80%; - flex: none; - display: block; - - /* padding-right: 5px; - border-right: 2px solid #CCC; */ - margin-right: 5px; -} - -.author { - color: #44B; - font-weight: bold; -} - -.timestamp { - font-size: 80%; - color: #BBB; -} diff --git a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html deleted file mode 100644 index 773e86c0..00000000 --- a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.html +++ /dev/null @@ -1,23 +0,0 @@ - - -
{{errorMessage}}
- -
-
-
In page: {{rootComment.page_title}}
-
- (conversation id: {{rootComment.id}}) -
-
- -
diff --git a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.spec.ts b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.spec.ts deleted file mode 100644 index bcac3f25..00000000 --- a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { CommentComponent } from './comment.component'; - -describe('CommentComponent', () => { - let component: CommentComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ CommentComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(CommentComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.ts b/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.ts deleted file mode 100644 index 48a570e7..00000000 --- a/view_conversations/conv-viewer-webapp/src/app/comment/comment.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import * as wpconvlib from '@conversationai/wpconvlib'; - -@Component({ - selector: 'app-comment', - templateUrl: './comment.component.html', - styleUrls: ['./comment.component.css'] -}) -export class CommentComponent implements OnInit { - @Input() comment: wpconvlib.Comment; - dbg: string; - - constructor() { } - - ngOnInit() { - this.dbg = JSON.stringify(this.comment, null, 2); - } - -} From 85e1db3d3d8d97fd46eb262a24ef44a4391ca8d7 Mon Sep 17 00:00:00 2001 From: Yiqing Hua Date: Mon, 30 Jul 2018 19:06:21 +0000 Subject: [PATCH 7/9] adding page link --- .../conv-viewer-webapp/src/app/app.component.css | 5 +++-- .../conv-viewer-webapp/src/app/app.component.html | 5 +++-- view_conversations/conv-viewer-webapp/src/app/app.module.ts | 2 -- .../src/app/conversation/conversation.component.html | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/view_conversations/conv-viewer-webapp/src/app/app.component.css b/view_conversations/conv-viewer-webapp/src/app/app.component.css index 0859644e..18eba160 100644 --- a/view_conversations/conv-viewer-webapp/src/app/app.component.css +++ b/view_conversations/conv-viewer-webapp/src/app/app.component.css @@ -124,12 +124,13 @@ last { } .collapsible-action { - background-color: Grey; + background-color: #EEE; color: #444; cursor: pointer; padding: 18px; width: 100%; - border: solid lightGrey; + border-left: 6px solid cornflowerblue; + border-bottom: solid grey; text-align: left; outline: none; font-size: 15px; diff --git a/view_conversations/conv-viewer-webapp/src/app/app.component.html b/view_conversations/conv-viewer-webapp/src/app/app.component.html index d8d1889b..7a43dfb1 100644 --- a/view_conversations/conv-viewer-webapp/src/app/app.component.html +++ b/view_conversations/conv-viewer-webapp/src/app/app.component.html @@ -83,7 +83,8 @@

@@ -95,7 +96,7 @@

{{errorMessage}}
-
In page: {{comment.rootComment.page_title}}
+
(conversation id: {{comment.rootComment.id}})
diff --git a/view_conversations/conv-viewer-webapp/src/app/app.module.ts b/view_conversations/conv-viewer-webapp/src/app/app.module.ts index aa12bfec..d288789c 100755 --- a/view_conversations/conv-viewer-webapp/src/app/app.module.ts +++ b/view_conversations/conv-viewer-webapp/src/app/app.module.ts @@ -40,7 +40,6 @@ import {HttpClientModule} from '@angular/common/http'; import { AppComponent } from './app.component'; import { ConversationComponent } from './conversation/conversation.component'; -import { CommentComponent } from './comment/comment.component'; @NgModule({ @@ -85,7 +84,6 @@ import { CommentComponent } from './comment/comment.component'; declarations: [ AppComponent, ConversationComponent, - CommentComponent, ], providers: [], bootstrap: [AppComponent] diff --git a/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.html b/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.html index 631db2cd..648d01a6 100644 --- a/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.html +++ b/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.html @@ -2,7 +2,8 @@

-
{{comment.user_text}}
+
{{comment.user_text}}
+
Anonymous
{{comment.timestamp}}
{{comment.cleaned_content}}
From 2c003f9b8ab73a03d98896541c34ee0bfdf8b236 Mon Sep 17 00:00:00 2001 From: Yiqing Hua Date: Tue, 31 Jul 2018 21:32:29 +0000 Subject: [PATCH 8/9] addressed comments --- crowdsourcing/wpconvlib/src/conversation.ts | 17 ++-- .../conv-viewer-webapi/src/db_types.ts | 7 +- .../conv-viewer-webapi/src/routes.ts | 32 ++++--- .../src/app/app.component.css | 5 + .../src/app/app.component.html | 2 +- .../src/app/app.component.ts | 95 +++++++++---------- .../conversation/conversation.component.css | 6 ++ .../conversation/conversation.component.html | 2 +- 8 files changed, 91 insertions(+), 75 deletions(-) diff --git a/crowdsourcing/wpconvlib/src/conversation.ts b/crowdsourcing/wpconvlib/src/conversation.ts index d4625c2e..13add936 100644 --- a/crowdsourcing/wpconvlib/src/conversation.ts +++ b/crowdsourcing/wpconvlib/src/conversation.ts @@ -41,6 +41,8 @@ export interface Comment { dfs_index?: number; // index according to Depth First Search of conv. // Starting here are toxicity scores, since they don't exist in all datasets // except in English, this fields are optional. + // TODO(yiqingh, ldixon) : decide on which scores to use, delete non-public + // ones from the spanner table. RockV6_1_FLIRTATION?: number | null, RockV6_1_GENDER?: number | null, RockV6_1_HEALTH_AGE_DISABILITY?: number | null, @@ -197,15 +199,18 @@ export function htmlForComment( // Walk down a comment and its children depth first. export function walkDfsComments( rootComment: Comment, f: (c: Comment) => void): void { - const commentsHtml = []; - const nextComment: Comment|undefined = rootComment; - if (nextComment) { + const stack = [rootComment]; + let nextComment: Comment|undefined = stack.pop(); + while (nextComment){ f(nextComment); - if (nextComment.children) { - for (const ch of nextComment.children) { - walkDfsComments(ch, f) + if (nextComment.children){ + if (nextComment.children.reverse()) { + for (const ch of nextComment.children) { + stack.push(ch) + } } } + nextComment = stack.pop(); } } diff --git a/view_conversations/conv-viewer-webapi/src/db_types.ts b/view_conversations/conv-viewer-webapi/src/db_types.ts index 7467ac0e..42bbec7f 100644 --- a/view_conversations/conv-viewer-webapi/src/db_types.ts +++ b/view_conversations/conv-viewer-webapi/src/db_types.ts @@ -128,6 +128,8 @@ class TimestampFieldHandler extends SpannerFieldHandler { interface HandlerSet { [fieldName:string] : SpannerFieldHandler; }; interface ParsedOutput { [fieldName:string] : string | string[] | Date | number | null; }; +const scoreSubstrings = 'RockV6_1|Smirnoff_2'; +const scoreType = 'score'; const handlers : Array> = [ new StringFieldHandler('id'), @@ -146,7 +148,7 @@ const handlers : Array> = new StringFieldHandler('type'), new IntFieldHandler('user_id'), new StringFieldHandler('user_text'), - new FloatFieldHandler('Score'), + new FloatFieldHandler(scoreType), ]; function addHandler(inputHandlers : HandlerSet, handler : SpannerFieldHandler) @@ -155,14 +157,13 @@ function addHandler(inputHandlers : HandlerSet, handler : SpannerFieldHandler(addHandler, {}); -const ScoreSubstrings = 'RockV6_1|Smirnoff_2'; export function parseOutputRows(rows: spanner.ResultRow[]) : T[] { const output : ParsedOutput[] = [] for (const row of rows) { const ret: { [fieldName:string] : string | string[] | Date | number | null } = {}; for (const field of row) { - const testname = new RegExp(ScoreSubstrings).test(field.name) ? 'Score' : field.name; + const testname = new RegExp(scoreSubstrings).test(field.name) ? scoreType : field.name; if(!(testname in handlerSet)) { console.error(`Field ${field.name} does not have a handler and so cannot be interpreted.`); break; diff --git a/view_conversations/conv-viewer-webapi/src/routes.ts b/view_conversations/conv-viewer-webapi/src/routes.ts index 73f5b48e..feb1aec0 100644 --- a/view_conversations/conv-viewer-webapi/src/routes.ts +++ b/view_conversations/conv-viewer-webapi/src/routes.ts @@ -33,21 +33,24 @@ const SEARCH_OP_TYPE = // escaped quotes. const SQL_SAFE_STRING = new runtime_types.RuntimeStringType('SearchBy', /^[^"]+$/); +const conversationIdIndex = '_by_conversation_id'; +const toxicityIndex = '_by_toxicity'; // TODO(ldixon): consider using passport auth // for google cloud project. export function setup( app: express.Express, conf: config.Config, spannerDatabase: spanner.Database) { - let table = `\`${conf.spannerTableName}\``; + const table = `\`${conf.spannerTableName}\``; app.get('/api/conversation-id/:conv_id', async (req, res) => { try { - let conv_id: runtime_types.ConversationId = + const conv_id: runtime_types.ConversationId = runtime_types.ConversationId.assert(req.params.conv_id); - const index = conf.spannerTableName + '_by_conversation_id'; + const index = conf.spannerTableName + conversationIdIndex; // TODO remove outer try wrapper unless it get used. + // Forcce Spanner using particular indices to speed up performance. const sqlQuery = `SELECT * FROM ${table}@{FORCE_INDEX=${index}} WHERE conversation_id="${conv_id}" @@ -72,8 +75,11 @@ export function setup( app.get('/api/toxicity/:score', async (req, res) => { try { - let score: number = req.params.score; - const index = conf.spannerTableName + '_by_toxicity'; + if isNaN(req.params.score) { + throw new Error(`Wanted number but got: NaN.`); + } + const score: number = req.params.score; + const index = conf.spannerTableName + toxicityIndex; // TODO remove outer try wrapper unless it get used. const sqlQuery = `SELECT * @@ -103,9 +109,9 @@ export function setup( app.get('/api/comment-id/:comment_id', async (req, res) => { try { - let comment_id: runtime_types.CommentId = + const comment_id: runtime_types.CommentId = runtime_types.CommentId.assert(req.params.comment_id); - const index = conf.spannerTableName + '_by_conversation_id'; + const index = conf.spannerTableName + conversationIdIndex; // TODO remove outer try wrapper unless it get used. @@ -136,7 +142,7 @@ export function setup( app.get('/api/revision-id/:rev_id', async (req, res) => { try { - let rev_id: runtime_types.RevisionId = + const rev_id: runtime_types.RevisionId = runtime_types.RevisionId.assert(req.params.rev_id); // TODO remove outer try wrapper unless it get used. @@ -164,7 +170,7 @@ export function setup( app.get('/api/page-id/:page_id', async (req, res) => { try { - let page_id: runtime_types.PageId = + const page_id: runtime_types.PageId = runtime_types.PageId.assert(req.params.page_id); // TODO remove outer try wrapper unless it get used. @@ -193,7 +199,7 @@ export function setup( app.get('/api/page-title/:page_title', async (req, res) => { try { - let page_title: runtime_types.PageTitleSearch = + const page_title: runtime_types.PageTitleSearch = runtime_types.PageTitleSearch.assert(req.params.page_title); // TODO remove outer try wrapper unless it get used. @@ -222,19 +228,19 @@ export function setup( app.get('/api/search/:search_by/:search_op/:search_for', async (req, res) => { if (!SEARCH_BY_TYPE.isValid(req.params.search_by)) { - let errorMsg = `Error: Invalid searchBy string: ${req.params.search_by}` + const errorMsg = `Error: Invalid searchBy string: ${req.params.search_by}` console.error(errorMsg); res.status(httpcodes.BAD_REQUEST).send(JSON.stringify({error: errorMsg})); return; } if (!SEARCH_OP_TYPE.isValid(req.params.search_op)) { - let errorMsg = `Error: Invalid searchOp string: ${req.params.search_op}` + const errorMsg = `Error: Invalid searchOp string: ${req.params.search_op}` console.error(errorMsg); res.status(httpcodes.BAD_REQUEST).send(JSON.stringify({error: errorMsg})); return; } if (!SQL_SAFE_STRING.isValid(req.params.search_for)) { - let errorMsg = `Error: Invalid searchFor string: ${req.params.search_for}` + const errorMsg = `Error: Invalid searchFor string: ${req.params.search_for}` console.error(errorMsg); res.status(httpcodes.BAD_REQUEST).send(JSON.stringify({error: errorMsg})); return; diff --git a/view_conversations/conv-viewer-webapp/src/app/app.component.css b/view_conversations/conv-viewer-webapp/src/app/app.component.css index 18eba160..f114455b 100644 --- a/view_conversations/conv-viewer-webapp/src/app/app.component.css +++ b/view_conversations/conv-viewer-webapp/src/app/app.component.css @@ -112,6 +112,11 @@ last { font-weight: bold; } +.anonAuthor { + color: Crimson; + font-weight: bold; +} + .timestamp { font-size: 80%; color: #BBB; diff --git a/view_conversations/conv-viewer-webapp/src/app/app.component.html b/view_conversations/conv-viewer-webapp/src/app/app.component.html index 7a43dfb1..00150e85 100644 --- a/view_conversations/conv-viewer-webapp/src/app/app.component.html +++ b/view_conversations/conv-viewer-webapp/src/app/app.component.html @@ -84,7 +84,7 @@

diff --git a/view_conversations/conv-viewer-webapp/src/app/app.component.ts b/view_conversations/conv-viewer-webapp/src/app/app.component.ts index f4ad07d9..aa10f0bd 100644 --- a/view_conversations/conv-viewer-webapp/src/app/app.component.ts +++ b/view_conversations/conv-viewer-webapp/src/app/app.component.ts @@ -34,6 +34,21 @@ interface HashObj { highlightId?: string; } +function highlightComments(actions : wpconvlib.Comment[], highlightId: string | undefined){ + const conversation: wpconvlib.Conversation = {}; + for (const a of actions) { + conversation[a.id] = a; + if (highlightId) { + if (highlightId === a.id) { + conversation[a.id].comment_to_highlight = a.id; + } else { + delete conversation[a.id].comment_to_highlight; + } + } + } + return conversation; +} + @Component({ selector: 'app-root', templateUrl: './app.component.html', @@ -122,6 +137,23 @@ export class AppComponent implements OnInit { document.location.hash = JSON.stringify(objToEncode); } + fetchConversations(actions: wpconvlib.Comment[]) { + this.searchResult = JSON.stringify(actions, null, 2); + delete this.inFlightRequest; + console.log('got conversation!'); + + const conversation: wpconvlib.Conversation = highlightComments(actions, this.highlightId); + console.log(conversation); + + this.rootComment = + wpconvlib.structureConversaton(conversation); + if (!this.rootComment) { + this.errorMessage = 'No Root comment in conversation'; + return; + } + } + + submitCommentSearch(comment : wpconvlib.Comment) { console.log('model-based form submitted'); this.errorMessage = null; @@ -133,31 +165,18 @@ export class AppComponent implements OnInit { '/api/comment-id/' + comment.id)) .subscribe( (actions: wpconvlib.Comment[]) => { - console.log('got conversation!'); - this.searchResult = JSON.stringify(actions, null, 2); - delete this.inFlightRequest; - - const conversation: wpconvlib.Conversation = {}; - for (const a of actions) { - conversation[a.id] = a; - if (this.highlightId) { - if (this.highlightId === a.id) { - conversation[a.id].comment_to_highlight = a.id; - } else { - delete conversation[a.id].comment_to_highlight; - } + this.searchResult = JSON.stringify(actions, null, 2); + delete this.inFlightRequest; + console.log('got conversation!'); + const conversation: wpconvlib.Conversation = highlightComments(actions, this.highlightId); + console.log(conversation); + comment.rootComment = + wpconvlib.structureConversaton(conversation); + if (!comment.rootComment) { + this.errorMessage = 'No Root comment in conversation'; + return; } - } - - console.log(conversation); - - comment.rootComment = - wpconvlib.structureConversaton(conversation); - if (!comment.rootComment) { - this.errorMessage = 'No Root comment in conversation'; - return; - } - }, + }, (e) => { console.log(e); if (e.error && e.error.error) { @@ -169,7 +188,6 @@ export class AppComponent implements OnInit { }); } - submitSearch() { console.log('model-based form submitted'); console.log(this.searchForm.value); @@ -182,32 +200,7 @@ export class AppComponent implements OnInit { '/api/' + URL_PART_FOR_SEARCHBY[this.searchForm.value.searchBy] + '/' + this.searchForm.value.searchFor)) .subscribe( - (actions: wpconvlib.Comment[]) => { - console.log('got conversation!'); - this.searchResult = JSON.stringify(actions, null, 2); - delete this.inFlightRequest; - - const conversation: wpconvlib.Conversation = {}; - for (const a of actions) { - conversation[a.id] = a; - if (this.highlightId) { - if (this.highlightId === a.id) { - conversation[a.id].comment_to_highlight = a.id; - } else { - delete conversation[a.id].comment_to_highlight; - } - } - } - - console.log(conversation); - - this.rootComment = - wpconvlib.structureConversaton(conversation); - if (!this.rootComment) { - this.errorMessage = 'No Root comment in conversation'; - return; - } - }, + this.fetchConversations, (e) => { console.log(e); if (e.error && e.error.error) { diff --git a/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.css b/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.css index bab23562..d1d9abaa 100644 --- a/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.css +++ b/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.css @@ -76,6 +76,12 @@ last { font-weight: bold; } +.anonAuthor { + color: Crimson; + font-weight: bold; +} + + .timestamp { font-size: 80%; color: #BBB; diff --git a/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.html b/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.html index 648d01a6..327ae1bc 100644 --- a/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.html +++ b/view_conversations/conv-viewer-webapp/src/app/conversation/conversation.component.html @@ -3,7 +3,7 @@ From 2f3fea6c03f2ce25235cd881d9a6a7914607f9cd Mon Sep 17 00:00:00 2001 From: Yiqing Hua Date: Wed, 1 Aug 2018 17:58:43 +0000 Subject: [PATCH 9/9] more addressing PR comments --- crowdsourcing/wpconvlib/src/conversation.ts | 7 +------ view_conversations/conv-viewer-webapi/src/routes.ts | 7 ++----- view_conversations/conv-viewer-webapi/src/runtime_types.ts | 7 +++++++ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/crowdsourcing/wpconvlib/src/conversation.ts b/crowdsourcing/wpconvlib/src/conversation.ts index 13add936..29199d89 100644 --- a/crowdsourcing/wpconvlib/src/conversation.ts +++ b/crowdsourcing/wpconvlib/src/conversation.ts @@ -43,22 +43,17 @@ export interface Comment { // except in English, this fields are optional. // TODO(yiqingh, ldixon) : decide on which scores to use, delete non-public // ones from the spanner table. - RockV6_1_FLIRTATION?: number | null, - RockV6_1_GENDER?: number | null, - RockV6_1_HEALTH_AGE_DISABILITY?: number | null, - RockV6_1_RELIGION?: number | null, - RockV6_1_RNE?: number | null, RockV6_1_SEVERE_TOXICITY?: number | null, RockV6_1_SEXUAL_ORIENTATION?: number | null, RockV6_1_SEXUALLY_EXPLICIT?: number | null, RockV6_1_TOXICITY?: number | null, + //TODO(yiqingh): change TOXICITY_IDENTITY_HATE to IDENTITY_ATTACK RockV6_1_TOXICITY_IDENTITY_HATE?: number | null, RockV6_1_TOXICITY_INSULT?: number | null, RockV6_1_TOXICITY_OBSCENE?: number | null, RockV6_1_TOXICITY_THREAT?: number | null, Smirnoff_2_ATTACK_ON_AUTHOR?: number | null, Smirnoff_2_ATTACK_ON_COMMENTER?: number | null, - Smirnoff_2_ATTACK_ON_PUBLISHER?: number | null, Smirnoff_2_INCOHERENT?: number | null, Smirnoff_2_INFLAMMATORY?: number | null, Smirnoff_2_LIKELY_TO_REJECT?: number | null, diff --git a/view_conversations/conv-viewer-webapi/src/routes.ts b/view_conversations/conv-viewer-webapi/src/routes.ts index feb1aec0..bae9239f 100644 --- a/view_conversations/conv-viewer-webapi/src/routes.ts +++ b/view_conversations/conv-viewer-webapi/src/routes.ts @@ -50,7 +50,7 @@ export function setup( const index = conf.spannerTableName + conversationIdIndex; // TODO remove outer try wrapper unless it get used. - // Forcce Spanner using particular indices to speed up performance. + // Force Spanner using particular indices to speed up performance. const sqlQuery = `SELECT * FROM ${table}@{FORCE_INDEX=${index}} WHERE conversation_id="${conv_id}" @@ -75,10 +75,7 @@ export function setup( app.get('/api/toxicity/:score', async (req, res) => { try { - if isNaN(req.params.score) { - throw new Error(`Wanted number but got: NaN.`); - } - const score: number = req.params.score; + const score: number = runtime_types.assertNumber(req.params.score); const index = conf.spannerTableName + toxicityIndex; // TODO remove outer try wrapper unless it get used. diff --git a/view_conversations/conv-viewer-webapi/src/runtime_types.ts b/view_conversations/conv-viewer-webapi/src/runtime_types.ts index 123f9a7a..acba70c6 100644 --- a/view_conversations/conv-viewer-webapi/src/runtime_types.ts +++ b/view_conversations/conv-viewer-webapi/src/runtime_types.ts @@ -88,3 +88,10 @@ export let PageId = new RuntimeStringType('PageId', /^(\d+)$/); export let PageTitleSearch = new RuntimeStringType('PageTitleSearch', /^([^"]+)$/); +export function assertNumber(score : number) { + if isNaN(score) { + throw new Error(`Wanted number but got: NaN.`); + } + return score; +} +
-
{{rootComment.user_text}}
+
{{rootComment.user_text}}
+
Anonymous
{{rootComment.timestamp}}
{{rootComment.cleaned_content}}
{{comment.user_text}}
-
Anonymous
+
Anonymous
{{comment.timestamp}}
{{comment.cleaned_content}}
{{rootComment.user_text}}
-
Anonymous
+
Anonymous
{{rootComment.timestamp}}
{{rootComment.cleaned_content}}