From 6a45379a3099a7846b1117992be1d35c450352ee Mon Sep 17 00:00:00 2001 From: akshatdalton Date: Tue, 13 Jul 2021 18:28:39 +0000 Subject: [PATCH] resolve topic: Add frontend support for `is:resolved` search keyword/filtering. This commit adds the frontend support for `is:resolved` search keyword. Test cases are added for the same. Fixes: #18990. --- frontend_tests/node_tests/filter.js | 43 +++++++++++++++++++ frontend_tests/node_tests/narrow_local.js | 49 ++++++++++++++++++++++ frontend_tests/node_tests/narrow_unread.js | 5 +++ static/js/filter.js | 19 ++++++++- 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/frontend_tests/node_tests/filter.js b/frontend_tests/node_tests/filter.js index fe1cab73d6..447157a48b 100644 --- a/frontend_tests/node_tests/filter.js +++ b/frontend_tests/node_tests/filter.js @@ -7,6 +7,7 @@ const {run_test} = require("../zjsunit/test"); const $ = require("../zjsunit/zjquery"); const {page_params} = require("../zjsunit/zpage_params"); +const message_edit = mock_esm("../../static/js/message_edit"); const message_store = mock_esm("../../static/js/message_store"); const stream_data = zrequire("stream_data"); @@ -201,6 +202,14 @@ test("basics", () => { assert.ok(filter.contains_only_private_messages()); assert.ok(!filter.has_operator("search")); assert.ok(filter.can_apply_locally()); + + operators = [{operator: "is", operand: "resolved"}]; + filter = new Filter(operators); + assert.ok(!filter.contains_only_private_messages()); + assert.ok(filter.can_mark_messages_read()); + assert.ok(!filter.has_operator("search")); + assert.ok(filter.can_apply_locally()); + assert.ok(!filter.is_personal_filter()); }); function assert_not_mark_read_with_has_operands(additional_operators_to_test) { @@ -266,6 +275,18 @@ function assert_not_mark_read_with_is_operands(additional_operators_to_test) { is_operator = [{operator: "is", operand: "unread", negated: true}]; filter = new Filter(additional_operators_to_test.concat(is_operator)); assert.ok(!filter.can_mark_messages_read()); + + is_operator = [{operator: "is", operand: "resolved"}]; + filter = new Filter(additional_operators_to_test.concat(is_operator)); + if (additional_operators_to_test.length === 0) { + assert.ok(filter.can_mark_messages_read()); + } else { + assert.ok(!filter.can_mark_messages_read()); + } + + is_operator = [{operator: "is", operand: "resolved", negated: true}]; + filter = new Filter(additional_operators_to_test.concat(is_operator)); + assert.ok(!filter.can_mark_messages_read()); } function assert_not_mark_read_when_searching(additional_operators_to_test) { @@ -605,6 +626,12 @@ test("predicate_basics", () => { predicate = get_predicate([["in", "all"]]); assert.ok(predicate({})); + predicate = get_predicate([["is", "resolved"]]); + const resolved_topic_name = message_edit.RESOLVED_TOPIC_PREFIX + "foo"; + assert.ok(predicate({type: "stream", topic: resolved_topic_name})); + assert.ok(!predicate({topic: resolved_topic_name})); + assert.ok(!predicate({type: "stream", topic: "foo"})); + const unknown_stream_id = 999; predicate = get_predicate([["in", "home"]]); assert.ok(!predicate({stream_id: unknown_stream_id, stream: "unknown"})); @@ -1190,6 +1217,14 @@ test("can_bucket_by", () => { filter = new Filter(terms); assert.equal(filter.can_bucket_by("is-mentioned"), false); assert.equal(filter.can_bucket_by("is-private"), false); + + terms = [{operator: "is", operand: "resolved"}]; + filter = new Filter(terms); + assert.equal(filter.can_bucket_by("stream", "topic"), false); + assert.equal(filter.can_bucket_by("stream"), false); + assert.equal(filter.can_bucket_by("pm-with"), false); + assert.equal(filter.can_bucket_by("is-mentioned"), false); + assert.equal(filter.can_bucket_by("is-private"), false); }); test("term_type", () => { @@ -1358,6 +1393,7 @@ test("navbar_helpers", () => { const is_starred = [{operator: "is", operand: "starred"}]; const is_private = [{operator: "is", operand: "private"}]; const is_mentioned = [{operator: "is", operand: "mentioned"}]; + const is_resolved = [{operator: "is", operand: "resolved"}]; const streams_public = [{operator: "streams", operand: "public"}]; const stream_topic_operators = [ {operator: "stream", operand: "foo"}, @@ -1416,6 +1452,13 @@ test("navbar_helpers", () => { title: "translated: Mentions", redirect_url_with_search: "/#narrow/is/mentioned", }, + { + operator: is_resolved, + is_common_narrow: true, + icon: "check", + title: "translated: Topics marked as resolved", + redirect_url_with_search: "/#narrow/topics/is/resolved", + }, { operator: stream_topic_operators, is_common_narrow: true, diff --git a/frontend_tests/node_tests/narrow_local.js b/frontend_tests/node_tests/narrow_local.js index df5e652ff7..8609788c29 100644 --- a/frontend_tests/node_tests/narrow_local.js +++ b/frontend_tests/node_tests/narrow_local.js @@ -6,6 +6,7 @@ const {mock_esm, zrequire} = require("../zjsunit/namespace"); const {run_test} = require("../zjsunit/test"); const all_messages_data = mock_esm("../../static/js/all_messages_data"); +const message_edit = mock_esm("../../static/js/message_edit"); const {Filter} = zrequire("../js/filter"); const {MessageListData} = zrequire("../js/message_list_data"); @@ -300,6 +301,54 @@ run_test("is:alerted with no unreads and one match", () => { test_with(fixture); }); +run_test("is:resolved with one unread", () => { + const resolved_topic_name = message_edit.RESOLVED_TOPIC_PREFIX + "foo"; + const fixture = { + filter_terms: [{operator: "is", operand: "resolved"}], + unread_info: { + flavor: "found", + msg_id: 56, + }, + has_found_newest: true, + all_messages: [ + {id: 55, type: "stream", topic: resolved_topic_name}, + {id: 56, type: "stream", topic: resolved_topic_name}, + {id: 57, type: "stream", topic: "foo"}, + ], + expected_id_info: { + target_id: undefined, + final_select_id: 56, + local_select_id: 56, + }, + expected_msg_ids: [55, 56], + }; + + test_with(fixture); +}); + +run_test("is:resolved with no unreads", () => { + const resolved_topic_name = message_edit.RESOLVED_TOPIC_PREFIX + "foo"; + const fixture = { + filter_terms: [{operator: "is", operand: "resolved"}], + unread_info: { + flavor: "not_found", + }, + has_found_newest: true, + all_messages: [ + {id: 55, type: "stream", topic: resolved_topic_name}, + {id: 57, type: "stream", topic: "foo"}, + ], + expected_id_info: { + target_id: undefined, + final_select_id: 55, + local_select_id: 55, + }, + expected_msg_ids: [55], + }; + + test_with(fixture); +}); + run_test("search", () => { const fixture = { filter_terms: [{operator: "search", operand: "whatever"}], diff --git a/frontend_tests/node_tests/narrow_unread.js b/frontend_tests/node_tests/narrow_unread.js index 03f4e63e84..2ab9ce271b 100644 --- a/frontend_tests/node_tests/narrow_unread.js +++ b/frontend_tests/node_tests/narrow_unread.js @@ -130,6 +130,11 @@ run_test("get_unread_ids", () => { unread_ids = candidate_ids(); assert.deepEqual(unread_ids, [stream_msg.id]); + terms = [{operator: "is", operand: "resolved"}]; + set_filter(terms); + unread_ids = candidate_ids(); + assert.deepEqual(unread_ids, [stream_msg.id]); + terms = [{operator: "sender", operand: "me@example.com"}]; set_filter(terms); // note that our candidate ids are just "all" ids now diff --git a/static/js/filter.js b/static/js/filter.js index c786d81812..a1ca94a714 100644 --- a/static/js/filter.js +++ b/static/js/filter.js @@ -2,6 +2,7 @@ import Handlebars from "handlebars/runtime"; import _ from "lodash"; import {$t} from "./i18n"; +import * as message_edit from "./message_edit"; import * as message_parser from "./message_parser"; import * as message_store from "./message_store"; import {page_params} from "./page_params"; @@ -92,6 +93,11 @@ function message_matches_search_term(message, operator, operand) { return message.alerted; case "unread": return unread.message_unread(message); + case "resolved": + return ( + message.type === "stream" && + message.topic.startsWith(message_edit.RESOLVED_TOPIC_PREFIX) + ); default: return false; // is:whatever returns false } @@ -451,6 +457,10 @@ export class Filter { return true; } + if (_.isEqual(term_types, ["is-resolved"])) { + return true; + } + if (_.isEqual(term_types, [])) { // All view return true; @@ -484,7 +494,7 @@ export class Filter { // can_mark_messages_read tests the following filters: // stream, stream + topic, // is: private, pm-with:, - // is: mentioned + // is: mentioned, is: resolved if (this.can_mark_messages_read()) { return true; } @@ -554,6 +564,8 @@ export class Filter { "/#narrow/pm-with/" + people.emails_to_slug(this.operands("pm-with").join(",")) ); + case "is-resolved": + return "/#narrow/topics/is/resolved"; // TODO: It is ambiguous how we want to handle the 'sender' case, // we may remove it in the future based on design decisions case "sender": @@ -590,6 +602,8 @@ export class Filter { return "at"; case "pm-with": return "envelope"; + case "is-resolved": + return "check"; default: return undefined; } @@ -640,6 +654,8 @@ export class Filter { // can have the same return type as other cases. return names.join(", "); } + case "is-resolved": + return $t({defaultMessage: "Topics marked as resolved"}); } } /* istanbul ignore next */ @@ -849,6 +865,7 @@ export class Filter { "is-private", "is-starred", "is-unread", + "is-resolved", "has-link", "has-image", "has-attachment",