From a5094c972a592aa1da3b5d674074e5a4e07e848f Mon Sep 17 00:00:00 2001 From: Ketan <73937490+devketanpro@users.noreply.github.com> Date: Mon, 4 Sep 2023 16:58:22 +0530 Subject: [PATCH 01/11] update elastic query for not planned coverage_status [NHUB-361] (#525) * update elastic query for not planned coverage_status [NHUB-361] * reformat code via black * fix test --- newsroom/agenda/agenda.py | 16 ++-------------- tests/core/test_agenda.py | 5 ++--- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/newsroom/agenda/agenda.py b/newsroom/agenda/agenda.py index 5ad4661a2..a24f906ac 100644 --- a/newsroom/agenda/agenda.py +++ b/newsroom/agenda/agenda.py @@ -570,23 +570,11 @@ def _filter_terms(filters, item_type): ) ) elif val == ["not planned"]: - must_not_term_filters.append( + must_term_filters.append( nested_query( path="coverages", query={ - "bool": { - "filter": [ - { - "terms": { - "coverages.coverage_status": [ - "coverage intended", - "coverage not decided yet", - "coverage upon request", - ] - } - } - ] - } + "bool": {"filter": [{"terms": {"coverages.coverage_status": ["coverage not intended"]}}]} }, name="coverage_status", ) diff --git a/tests/core/test_agenda.py b/tests/core/test_agenda.py index 5efceb866..83ddcd281 100644 --- a/tests/core/test_agenda.py +++ b/tests/core/test_agenda.py @@ -629,9 +629,8 @@ def test_filter_agenda_by_coverage_status(client): assert "foo" == data["_items"][0]["_id"] data = get_json(client, '/agenda/search?filter={"coverage_status":["not planned"]}') - assert 3 == data["_meta"]["total"] - assert "baz" == data["_items"][0]["_id"] - assert "bar" == data["_items"][1]["_id"] + assert 1 == data["_meta"]["total"] + assert "bar" == data["_items"][0]["_id"] data = get_json(client, '/agenda/search?filter={"coverage_status":["may be"]}') assert 1 == data["_meta"]["total"] From 62c0c6ab5bf9c297f7996279f1cce5a074dea446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ja=C5=A1ek?= Date: Tue, 5 Sep 2023 10:56:56 +0200 Subject: [PATCH 02/11] avoid AAP references in default theme (#526) NHUB-12 --- assets/styles/custom.scss | 2 +- newsroom/templates/base_layout.html | 2 +- newsroom/templates/company_expiry_alert_user.html | 2 +- newsroom/templates/company_expiry_alert_user.txt | 2 +- newsroom/templates/page-copyright.html | 8 ++++---- newsroom/web/default_settings.py | 4 ++-- tests/core/test_agenda.py | 2 +- tests/core/test_topics.py | 4 ++-- tests/core/test_wire.py | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/assets/styles/custom.scss b/assets/styles/custom.scss index 5e0a75131..15ec6cee8 100644 --- a/assets/styles/custom.scss +++ b/assets/styles/custom.scss @@ -1,4 +1,4 @@ -// Bootstrap overrides - AAP Newsroom +// Bootstrap overrides - Newshub @import './custom-variables.scss'; // custom variables, Bootstrap overrides @import './custom-utilities.scss'; // custom utilities for RTL support diff --git a/newsroom/templates/base_layout.html b/newsroom/templates/base_layout.html index 78bac03cc..1b3a6f1eb 100644 --- a/newsroom/templates/base_layout.html +++ b/newsroom/templates/base_layout.html @@ -5,7 +5,7 @@ {% block title_wrapper %} - {{ config.SITE_NAME }} - {% block title %}{% endblock %} + {{ config.SITE_NAME }}{% if self.title() %} - {% endif %}{% block title %}{% endblock %} {% endblock %} {% block fonts %} diff --git a/newsroom/templates/company_expiry_alert_user.html b/newsroom/templates/company_expiry_alert_user.html index 873f8d863..53f305d48 100644 --- a/newsroom/templates/company_expiry_alert_user.html +++ b/newsroom/templates/company_expiry_alert_user.html @@ -1,3 +1,3 @@

Your company's account is expiring on {{expiry_date | datetime_short}}


-

Please contact AAP for further details.

+

Please contact us for further details.

diff --git a/newsroom/templates/company_expiry_alert_user.txt b/newsroom/templates/company_expiry_alert_user.txt index 6ddc918cc..b2c5a89f7 100644 --- a/newsroom/templates/company_expiry_alert_user.txt +++ b/newsroom/templates/company_expiry_alert_user.txt @@ -1,2 +1,2 @@ Your company's account is expiring on {{expiry_date | datetime_short}} -Please contact AAP for further details. +Please contact us for further details. diff --git a/newsroom/templates/page-copyright.html b/newsroom/templates/page-copyright.html index 9301eb871..d1d9e6bdc 100644 --- a/newsroom/templates/page-copyright.html +++ b/newsroom/templates/page-copyright.html @@ -9,16 +9,16 @@

Copyright

-

Note: The copyright in the information provided through AAP Newsroom is either owned by - or licensed to Australian Associated Press Pty Ltd.

+

Note: The copyright in the information provided through {{ config.SITE_NAME }} is either owned by + or licensed to {{ config.COPYRIGHT_HOLDER }}.

You may not use this information for any purpose or in any medium or format unless you or your - organisation have an appropriate agreement in place with Australian Associated Press Pty Ltd and + organisation have an appropriate agreement in place with {{ config.COPYRIGHT_HOLDER }} and strictly adhere to the terms of such agreement.

For the avoidance of doubt, this information (including edited, abridged or rewritten versions of this information) is not to be republished, redistributed or redisseminated via the Internet or - the World Wide Web without AAP's written permission. The above prohibitions include framing, + the World Wide Web without written permission. The above prohibitions include framing, linking, posting to newsgroups and any other form of copying.

diff --git a/newsroom/web/default_settings.py b/newsroom/web/default_settings.py index 96012c5a0..a293694d3 100644 --- a/newsroom/web/default_settings.py +++ b/newsroom/web/default_settings.py @@ -176,8 +176,8 @@ "newsroom.notifications.send_scheduled_notifications", ] -SITE_NAME = "AAP Newsroom" -COPYRIGHT_HOLDER = "AAP" +SITE_NAME = "Newshub" +COPYRIGHT_HOLDER = "Sourcefabric" COPYRIGHT_NOTICE = "" USAGE_TERMS = "" CONTACT_ADDRESS = "#" diff --git a/tests/core/test_agenda.py b/tests/core/test_agenda.py index 83ddcd281..126bdfd13 100644 --- a/tests/core/test_agenda.py +++ b/tests/core/test_agenda.py @@ -236,7 +236,7 @@ def test_share_items(client, app, mocker): assert resp.status_code == 201, resp.get_data().decode("utf-8") assert len(outbox) == 1 assert outbox[0].recipients == ["foo2@bar.com"] - assert outbox[0].subject == "From AAP Newsroom: test headline" + assert outbox[0].subject == "From Newshub: test headline" assert "Hi Foo Bar" in outbox[0].body assert "admin admin (admin@sourcefabric.org) shared " in outbox[0].body assert "Conference Planning" in outbox[0].body diff --git a/tests/core/test_topics.py b/tests/core/test_topics.py index d8646d316..621682b53 100644 --- a/tests/core/test_topics.py +++ b/tests/core/test_topics.py @@ -153,7 +153,7 @@ def test_share_wire_topics(client, app): assert len(outbox) == 1 assert outbox[0].recipients == ["test@bar.com"] assert outbox[0].sender == "newsroom@localhost" - assert outbox[0].subject == "From AAP Newsroom: %s" % topic["label"] + assert outbox[0].subject == "From Newshub: %s" % topic["label"] assert "Hi Test Bar" in outbox[0].body assert "Foo Bar (foo@bar.com) shared " in outbox[0].body assert topic["query"] in outbox[0].body @@ -183,7 +183,7 @@ def test_share_agenda_topics(client, app): assert len(outbox) == 1 assert outbox[0].recipients == ["test@bar.com"] assert outbox[0].sender == "newsroom@localhost" - assert outbox[0].subject == "From AAP Newsroom: %s" % agenda_topic["label"] + assert outbox[0].subject == "From Newshub: %s" % agenda_topic["label"] assert "Hi Test Bar" in outbox[0].body assert "Foo Bar (foo@bar.com) shared " in outbox[0].body assert agenda_topic["query"] in outbox[0].body diff --git a/tests/core/test_wire.py b/tests/core/test_wire.py index 99ab867f9..671c9d2fd 100644 --- a/tests/core/test_wire.py +++ b/tests/core/test_wire.py @@ -68,7 +68,7 @@ def test_share_items(client, app): assert len(outbox) == 1 assert outbox[0].recipients == ["foo2@bar.com"] assert outbox[0].sender == "newsroom@localhost" - assert outbox[0].subject == "From AAP Newsroom: %s" % items[0]["headline"] + assert outbox[0].subject == "From Newshub: %s" % items[0]["headline"] assert "Hi Foo Bar" in outbox[0].body assert "admin admin (admin@sourcefabric.org) shared " in outbox[0].body assert items[0]["headline"] in outbox[0].body From 9602964b65cd75f3f61aa3f802561cbab7aabc94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ja=C5=A1ek?= Date: Tue, 5 Sep 2023 14:05:58 +0200 Subject: [PATCH 03/11] use elastic cross fields search by default (#527) * use elastic cross fields search by default CPCN-316 --- newsroom/search/service.py | 45 +++++++++++++++++------------ tests/search/test_search_fields.py | 20 +++++++++++++ tests/search/test_search_filters.py | 5 ++++ 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/newsroom/search/service.py b/newsroom/search/service.py index 03b22ff8f..4d4086054 100644 --- a/newsroom/search/service.py +++ b/newsroom/search/service.py @@ -1,5 +1,5 @@ import logging -from typing import List, Optional, Union, Dict, Any, TypedDict +from typing import List, Literal, Optional, Union, Dict, Any, TypedDict from copy import deepcopy from flask import current_app as app, json, abort @@ -41,15 +41,22 @@ def strtobool(val): return _strtobool(val) -def query_string(query, default_operator="AND", fields=["*"]): +def query_string( + query: str, + default_operator: Literal["AND", "OR"] = "AND", + fields: List[str] = ["*"], + multimatch_type: Literal["cross_fields", "best_fields"] = "cross_fields", + analyze_wildcard=False, +): query_string_settings = app.config["ELASTICSEARCH_SETTINGS"]["settings"]["query_string"] return { "query_string": { "query": query, "default_operator": default_operator, - "analyze_wildcard": query_string_settings["analyze_wildcard"], + "analyze_wildcard": query_string_settings["analyze_wildcard"] or analyze_wildcard, "lenient": True, "fields": fields, + "type": multimatch_type, } } @@ -772,28 +779,30 @@ def apply_request_advanced_search(self, search: SearchQuery): if not fields: return - def gen_advanced_query(keywords: str, operator: str, multi_match_type: str): - return { - "query_string": { - "query": keywords, - "fields": fields, - "default_operator": operator, - "type": multi_match_type, - "lenient": True, - "analyze_wildcard": True, - }, - } - if search.advanced.get("all"): search.query["bool"].setdefault("must", []).append( - gen_advanced_query(search.advanced["all"], "AND", "cross_fields") + query_string( + search.advanced["all"], "AND", fields=fields, multimatch_type="cross_fields", analyze_wildcard=True + ) ) + if search.advanced.get("any"): search.query["bool"].setdefault("must", []).append( - gen_advanced_query(search.advanced["any"], "OR", "best_fields") + query_string( + search.advanced["any"], "OR", fields=fields, multimatch_type="best_fields", analyze_wildcard=True + ) ) + if search.advanced.get("exclude"): - search.query["bool"]["must_not"].append(gen_advanced_query(search.advanced["exclude"], "OR", "best_fields")) + search.query["bool"]["must_not"].append( + query_string( + search.advanced["exclude"], + "OR", + fields=fields, + multimatch_type="best_fields", + analyze_wildcard=True, + ) + ) def apply_embargoed_filters(self, search): """Generate filters for embargoed params""" diff --git a/tests/search/test_search_fields.py b/tests/search/test_search_fields.py index 3deca7f99..1b43869c4 100644 --- a/tests/search/test_search_fields.py +++ b/tests/search/test_search_fields.py @@ -1,3 +1,5 @@ +from flask import json +from urllib.parse import quote from tests.utils import get_json @@ -16,6 +18,24 @@ def test_wire_search_fields(client, app): assert 0 == len(data["_items"]) +def test_wire_search_cross_fields(client, app): + app.data.insert( + "items", + [ + {"headline": "foo", "body_html": "bar", "type": "text"}, + ], + ) + + data = get_json(client, "/wire/search?q=foo+bar") + assert 1 == len(data["_items"]) + + data = get_json(client, f'/wire/search?advanced={quote(json.dumps({"all": "foo bar"}))}') + assert 1 == len(data["_items"]) + + data = get_json(client, f'/wire/search?advanced={quote(json.dumps({"any": "foo bar"}))}') + assert 1 == len(data["_items"]) + + def test_agenda_search_fields(client, app): app.data.insert( "agenda", diff --git a/tests/search/test_search_filters.py b/tests/search/test_search_filters.py index f1f84c4d6..649759107 100644 --- a/tests/search/test_search_filters.py +++ b/tests/search/test_search_filters.py @@ -54,6 +54,7 @@ def test_apply_section_filter(client, app): "analyze_wildcard": query_string_settings["analyze_wildcard"], "lenient": True, "fields": ["*"], + "type": "cross_fields", } } in search.query["bool"]["filter"] @@ -68,6 +69,7 @@ def test_apply_section_filter(client, app): "analyze_wildcard": query_string_settings["analyze_wildcard"], "lenient": True, "fields": ["*"], + "type": "cross_fields", } } in search.query["bool"]["filter"] @@ -147,6 +149,7 @@ def assert_products_query(user_id, args=None, products=None): "analyze_wildcard": query_string_settings["analyze_wildcard"], "lenient": True, "fields": app.config["WIRE_SEARCH_FIELDS"], + "type": "cross_fields", } } in search.query["bool"]["should"] @@ -182,6 +185,7 @@ def test_apply_request_filter__query_string(client, app): "analyze_wildcard": query_string_settings["analyze_wildcard"], "lenient": True, "fields": app.config["WIRE_SEARCH_FIELDS"], + "type": "cross_fields", } } in search.query["bool"]["must"] @@ -194,6 +198,7 @@ def test_apply_request_filter__query_string(client, app): "analyze_wildcard": query_string_settings["analyze_wildcard"], "lenient": True, "fields": app.config["WIRE_SEARCH_FIELDS"], + "type": "cross_fields", } } in search.query["bool"]["must"] From 193a6bbd01d43d56adb3e78ae923fb10dd248f77 Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Wed, 6 Sep 2023 13:37:31 +0300 Subject: [PATCH 04/11] Initially open some of the dropdowns (#519) * Initially open some of the dropdowns * Reorder sections * e2e fix * Changes after review * Fixes --- .../{FormSection.jsx => FormSection.tsx} | 20 ++--- .../SearchResultsTopicRow.tsx | 2 +- .../components/SearchResultsBar/index.tsx | 3 +- assets/search/components/TopicForm.tsx | 88 +++++++++---------- assets/wire/components/WireApp.tsx | 2 +- e2e/cypress/e2e/wire/advanced_search.cy.js | 2 - e2e/cypress/e2e/wire/topics.cy.js | 13 +-- 7 files changed, 62 insertions(+), 68 deletions(-) rename assets/components/{FormSection.jsx => FormSection.tsx} (68%) diff --git a/assets/components/FormSection.jsx b/assets/components/FormSection.tsx similarity index 68% rename from assets/components/FormSection.jsx rename to assets/components/FormSection.tsx index e45f87e1c..704e41a15 100644 --- a/assets/components/FormSection.jsx +++ b/assets/components/FormSection.tsx @@ -1,11 +1,17 @@ import React, {useState} from 'react'; -import PropTypes from 'prop-types'; -export function FormSection({name, testId, children}) { - const [opened, setOpened] = useState(name == null); +interface IProps { + initiallyOpen?: boolean; + name: string; + dataTestId?: string; + children: JSX.Element; +} + +export function FormSection({initiallyOpen, name, children, dataTestId}: IProps) { + const [opened, setOpened] = useState(initiallyOpen ?? name == null); return ( -
+
); } - -FormSection.propTypes = { - name: PropTypes.string.isRequired, - testId: PropTypes.string, - children: PropTypes.node, -}; diff --git a/assets/search/components/SearchResultsBar/SearchResultsTopicRow.tsx b/assets/search/components/SearchResultsBar/SearchResultsTopicRow.tsx index 227c8357c..862c4e937 100644 --- a/assets/search/components/SearchResultsBar/SearchResultsTopicRow.tsx +++ b/assets/search/components/SearchResultsBar/SearchResultsTopicRow.tsx @@ -58,7 +58,7 @@ export function SearchResultsTopicRow({ ); } - if (get(searchParams, 'navigation.length', 0)) { + if ((searchParams.navigation?.length ?? 0) > 0 && (Object.keys(navigations ?? []).length > 0)) { searchParams.navigation.forEach((navId) => { const navigation = navigations[navId]; diff --git a/assets/search/components/SearchResultsBar/index.tsx b/assets/search/components/SearchResultsBar/index.tsx index 3de290b6e..a4e08c4d1 100644 --- a/assets/search/components/SearchResultsBar/index.tsx +++ b/assets/search/components/SearchResultsBar/index.tsx @@ -48,7 +48,7 @@ class SearchResultsBarComponent extends React.Component { const urlParams = new URLSearchParams(window.location.search); this.state = { - isTagSectionShown: urlParams.get('topic') != null, + isTagSectionShown: this.props.initiallyOpen || urlParams.get('topic') != null, sortValue: this.sortValues[0].label, }; this.toggleTagSection = this.toggleTagSection.bind(this); @@ -224,6 +224,7 @@ class SearchResultsBarComponent extends React.Component { SearchResultsBarComponent.propTypes = { user: PropTypes.object, + initiallyOpen: PropTypes.bool, minimizeSearchResults: PropTypes.bool, showTotalItems: PropTypes.bool, diff --git a/assets/search/components/TopicForm.tsx b/assets/search/components/TopicForm.tsx index 8dd51b7a1..4750209a5 100644 --- a/assets/search/components/TopicForm.tsx +++ b/assets/search/components/TopicForm.tsx @@ -114,7 +114,49 @@ const TopicForm: React.FC = ({
- + +
0 ? '' : ' nh-container--highlight text-start')}> + {folders.length > 0 + ? ( + + {readOnly !== true && topic.folder && ( + + )} + + {readOnly !== true && folders.map((folder: any) => ( + + ))} + + ) + : ( +

{gettext('To organize your topics, please create a folder in the “My Wire Topics” section.')}

+ ) + } +
+
+
- -
0 ? '' : ' nh-container--highlight text-start')}> - {folders.length > 0 - ? ( - - {readOnly !== true && topic.folder && ( - - )} - - {readOnly !== true && folders.map((folder: any) => ( - - ))} - - ) - : ( -

{gettext('To organize your topics, please create a folder in the “My Wire Topics” section.')}

- ) - } -
-
- + Date: Thu, 7 Sep 2023 09:41:18 +0530 Subject: [PATCH 05/11] If an item with just one completed Coverage is there, it should not show up as Planned [NHUB-361] (#530) update elastic query for planned coverage [NHUB-361] --- newsroom/agenda/agenda.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/newsroom/agenda/agenda.py b/newsroom/agenda/agenda.py index a24f906ac..a9e477744 100644 --- a/newsroom/agenda/agenda.py +++ b/newsroom/agenda/agenda.py @@ -548,6 +548,12 @@ def _filter_terms(filters, item_type): name="coverage_status", ) ) + must_not_term_filters.append( + nested_query( + path="coverages", + query={"bool": {"must": [{"exists": {"field": "coverages.delivery_id"}}]}}, + ) + ) elif val == ["may be"]: must_term_filters.append( nested_query( From ac1a1eeb46c993407ce8085d88e609afc27fee52 Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Thu, 7 Sep 2023 11:01:12 +0300 Subject: [PATCH 06/11] If the client doesn't have wire configured don't show dashboard personalization option (#528) --- assets/home/components/HomeApp.tsx | 131 +++++++++++++++-------------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/assets/home/components/HomeApp.tsx b/assets/home/components/HomeApp.tsx index 7429a9924..07d5f454a 100644 --- a/assets/home/components/HomeApp.tsx +++ b/assets/home/components/HomeApp.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {connect} from 'react-redux'; -import {gettext, isDisplayed, isMobilePhone} from 'utils'; +import {getConfig, gettext, isDisplayed, isMobilePhone} from 'utils'; import {get} from 'lodash'; import {getCard} from 'components/cards/utils'; import getItemActions from 'wire/item-actions'; @@ -22,6 +22,8 @@ import {getCurrentUser} from 'company-admin/selectors'; import {IPersonalizedDashboardsWithData} from 'home/reducers'; import {ITopic} from 'interfaces/topic'; +export const WIRE_SECTION = 'wire'; + const modals: any = { shareItem: ShareItemModal, downloadItems: DownloadItemsModal, @@ -61,6 +63,7 @@ interface IProps { filterGroupLabels: any; currentUser: any; fetchItems: () => any; + userSections: any; } class HomeApp extends React.Component { @@ -230,6 +233,7 @@ class HomeApp extends React.Component { renderContent(children?: any): any { const {cards} = this.props; + const isWireSectionConfigured = this.props.userSections[WIRE_SECTION] != null; return ( @@ -243,67 +247,69 @@ class HomeApp extends React.Component { ref={(elem: any) => this.elem = elem} >
-
- { - !this.hasPersonalDashboard ? ( -
- -
- ) : ( - -
- { - if (optionId === 'default' || optionId === 'my-home') { - this.setState({ - activeOptionId: optionId - }); - } - }} - /> - -
- - ) - } -
+ { + isWireSectionConfigured && ( +
+ { + !this.hasPersonalDashboard ? ( +
+ +
+ ) : ( +
+ { + if (optionId === 'default' || optionId === 'my-home') { + this.setState({ + activeOptionId: optionId + }); + } + }} + /> + +
+ ) + } +
+ ) + } { this.props.modal?.modal === 'personalizeHome' && ( ({ products: state.products, user: state.user, userType: state.userType, + userSections: state.userSections, company: state.company, itemToOpen: state.itemToOpen, modal: state.modal, From abccbf7748ebf102c053deef2307e13807943cf3 Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Thu, 7 Sep 2023 13:29:49 +0300 Subject: [PATCH 07/11] Improve initially open condition (#532) --- .../components/SearchResultsBar/index.tsx | 43 ++++++++++++------- assets/wire/components/WireApp.tsx | 2 +- e2e/cypress/e2e/wire/advanced_search.cy.js | 2 + 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/assets/search/components/SearchResultsBar/index.tsx b/assets/search/components/SearchResultsBar/index.tsx index a4e08c4d1..4cead331a 100644 --- a/assets/search/components/SearchResultsBar/index.tsx +++ b/assets/search/components/SearchResultsBar/index.tsx @@ -25,32 +25,35 @@ import NewItemsIcon from '../NewItemsIcon'; class SearchResultsBarComponent extends React.Component { - sortValues = [ - { - label: gettext('Date (Newest)'), - sortFunction: () => this.setSortQuery('versioncreated:desc'), - }, - { - label: gettext('Date (Oldest)'), - sortFunction: () => this.setSortQuery('versioncreated:asc'), - }, - { - label: gettext('Relevance'), - sortFunction: () => this.setSortQuery('_score'), - }, - ]; + private sortValues: Array<{label: string; sortFunction: () => void;}>; + private topicNotNull: boolean; static propTypes: any; static defaultProps: any; constructor(props: any) { super(props); - const urlParams = new URLSearchParams(window.location.search); + this.topicNotNull = new URLSearchParams(window.location.search).get('topic') != null; + this.sortValues = [ + { + label: gettext('Date (Newest)'), + sortFunction: () => this.setSortQuery('versioncreated:desc'), + }, + { + label: gettext('Date (Oldest)'), + sortFunction: () => this.setSortQuery('versioncreated:asc'), + }, + { + label: gettext('Relevance'), + sortFunction: () => this.setSortQuery('_score'), + }, + ]; this.state = { - isTagSectionShown: this.props.initiallyOpen || urlParams.get('topic') != null, + isTagSectionShown: this.props.initiallyOpen || this.topicNotNull, sortValue: this.sortValues[0].label, }; + this.toggleTagSection = this.toggleTagSection.bind(this); this.toggleNavigation = this.toggleNavigation.bind(this); this.setQuery = this.setQuery.bind(this); @@ -63,6 +66,14 @@ class SearchResultsBarComponent extends React.Component { this.resetFilter = this.resetFilter.bind(this); } + componentDidUpdate(prevProps: any): void { + if (prevProps.initiallyOpen != this.props.initiallyOpen) { + this.setState({ + isTagSectionShown: this.props.initiallyOpen || this.topicNotNull, + }); + } + } + toggleTagSection() { this.setState((prevState: any) => ({isTagSectionShown: !prevState.isTagSectionShown})); } diff --git a/assets/wire/components/WireApp.tsx b/assets/wire/components/WireApp.tsx index 0d29da97e..7c1d32d52 100644 --- a/assets/wire/components/WireApp.tsx +++ b/assets/wire/components/WireApp.tsx @@ -216,7 +216,7 @@ class WireApp extends BaseApp { > Date: Thu, 7 Sep 2023 21:22:37 +0530 Subject: [PATCH 08/11] Fix : Remove 'Contact Us' link on Login header [CPCN-287] (#531) * Remove 'Contact Us' link on Login header [CPCN-287] * addressed comment --- newsroom/templates/base_layout.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/newsroom/templates/base_layout.html b/newsroom/templates/base_layout.html index 1b3a6f1eb..d7cbc1dce 100644 --- a/newsroom/templates/base_layout.html +++ b/newsroom/templates/base_layout.html @@ -51,9 +51,11 @@

{{ gettext('Main Navigation Bar') }}

{% endif %} From 2e399d74d10b91bcaef6ef762f6d86b5609d8f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ja=C5=A1ek?= Date: Fri, 8 Sep 2023 06:41:06 +0200 Subject: [PATCH 09/11] fix topics subscription for public users (#535) NHUB-381 --- newsroom/auth/utils.py | 32 ++++++++++++++++++-------------- newsroom/users/users.py | 14 ++++++-------- tests/core/test_topics.py | 7 +++---- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/newsroom/auth/utils.py b/newsroom/auth/utils.py index 43f19e51d..92bbf1d89 100644 --- a/newsroom/auth/utils.py +++ b/newsroom/auth/utils.py @@ -85,20 +85,24 @@ def redirect_with_error(error_str): return flask.redirect(flask.url_for(redirect_on_success)) -def start_user_session(user: User, permanent=False): - flask.session["user"] = str(user["_id"]) # str to avoid serialization issues - flask.session["name"] = "{} {}".format(user.get("first_name"), user.get("last_name")) - flask.session["user_type"] = user["user_type"] - flask.session["auth_ttl"] = utcnow().replace(tzinfo=None) + SESSION_AUTH_TTL - flask.session.permanent = permanent - - -def clear_user_session(): - flask.session["user"] = None - flask.session["name"] = None - flask.session["user_type"] = None - flask.session["auth_ttl"] = None - flask.session["auth_user"] = None +def start_user_session(user: User, permanent=False, session=None): + if session is None: + session = flask.session + session["user"] = str(user["_id"]) # str to avoid serialization issues + session["name"] = "{} {}".format(user.get("first_name"), user.get("last_name")) + session["user_type"] = user["user_type"] + session["auth_ttl"] = utcnow().replace(tzinfo=None) + SESSION_AUTH_TTL + session.permanent = permanent + + +def clear_user_session(session=None): + if session is None: + session = flask.session + session["user"] = None + session["name"] = None + session["user_type"] = None + session["auth_ttl"] = None + session["auth_user"] = None def is_current_user_admin() -> bool: diff --git a/newsroom/users/users.py b/newsroom/users/users.py index 07c248b52..a403c310d 100644 --- a/newsroom/users/users.py +++ b/newsroom/users/users.py @@ -309,15 +309,13 @@ def check_permissions(self, doc, updates=None): elif request and request.method == "DELETE" and doc.get("_id") != manager.get("_id"): return - if request and request.url_rule and request.url_rule.rule: + if request.url_rule and request.url_rule.rule: if request.url_rule.rule in ["/reset_password/", "/token/"]: return - elif request.url_rule.rule in [ - "/users/<_id>", - "/users/<_id>/profile", - "/users/<_id>/notification_schedules", - ] or (request.endpoint and "|item" in request.endpoint and request.method == "PATCH"): - if not updated_fields or all([key in USER_PROFILE_UPDATES for key in updated_fields]): - return + if request.method != "DELETE" and ( + not updated_fields or all([key in USER_PROFILE_UPDATES for key in updated_fields]) + ): + return + abort(403) diff --git a/tests/core/test_topics.py b/tests/core/test_topics.py index 621682b53..dcad5788c 100644 --- a/tests/core/test_topics.py +++ b/tests/core/test_topics.py @@ -2,6 +2,7 @@ from unittest import mock from pytest import fixture from copy import deepcopy +from newsroom.auth.utils import start_user_session from newsroom.topics.views import get_topic_url from ..fixtures import ( # noqa: F401 @@ -38,12 +39,10 @@ @fixture -def auth_client(client): +def auth_client(client, public_user): with client as cli: with client.session_transaction() as session: - session["user"] = PUBLIC_USER_ID - session["name"] = PUBLIC_USER_NAME - session["company"] = COMPANY_1_ID + start_user_session(public_user, session=session) yield cli From 4a9b732271adabb5e98bbb64c0cbc4d93f5e89cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ja=C5=A1ek?= Date: Fri, 8 Sep 2023 06:41:27 +0200 Subject: [PATCH 10/11] populate SERVER_NAME based on CLIENT_URL (#533) * populate SERVER_NAME based on CLIENT_URL NHUB-380 * revert the SERVER_NAME config it would prevent running on multiple domains --- newsroom/web/default_settings.py | 1 + newsroom/web/worker.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/newsroom/web/default_settings.py b/newsroom/web/default_settings.py index a293694d3..7ada18157 100644 --- a/newsroom/web/default_settings.py +++ b/newsroom/web/default_settings.py @@ -212,6 +212,7 @@ #: public client url - used to create links within emails etc CLIENT_URL = os.environ.get("CLIENT_URL", "http://localhost:5050") +PREFERRED_URL_SCHEME = os.environ.get("PREFERRED_URL_SCHEME") or ("https" if "https://" in CLIENT_URL else "http") MEDIA_PREFIX = os.environ.get("MEDIA_PREFIX", "/assets") diff --git a/newsroom/web/worker.py b/newsroom/web/worker.py index ba72eedd1..6297abc33 100644 --- a/newsroom/web/worker.py +++ b/newsroom/web/worker.py @@ -22,4 +22,5 @@ celery = app.celery # Set ``SERVER_NAME`` so ``url_for(_external=True)`` works -app.config["SERVER_NAME"] = urlparse(app.config["CLIENT_URL"]).netloc or None +# and only set it here so that web server can listeon on multiple domains +app.config.setdefault("SERVER_NAME", urlparse(app.config["CLIENT_URL"]).netloc or None) From e750740d5b169d429d6b027ba2a14eb16137d9a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ja=C5=A1ek?= Date: Fri, 8 Sep 2023 10:19:36 +0200 Subject: [PATCH 11/11] fix agenda bookmarks not visible (#536) it was limiting it based on current date but bookmarked events might be shown anytime, so limit it to be max 10 days from the start. NHUB-335 --- assets/agenda/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/agenda/utils.ts b/assets/agenda/utils.ts index 59dba7d02..c151cc399 100644 --- a/assets/agenda/utils.ts +++ b/assets/agenda/utils.ts @@ -712,7 +712,7 @@ const isBetweenDay = (day: moment.Moment, start: moment.Moment, end: moment.Mome return day.isSameOrAfter(start) && testDay.isSameOrBefore(endDate); } - return day.isBetween(startDate, endDate, 'day', '[]'); + return testDay.isBetween(startDate, endDate, 'day', '[]'); }; /** @@ -757,7 +757,7 @@ export function groupItems(items: any, activeDate: any, activeGrouping: any, fea } let key = null; - end = moment.min(end, minStart.clone().add(10, 'd')); // show each event for 10 days max not to destroy the UI + end = moment.min(end, start.clone().add(10, 'd')); // show each event for 10 days max not to destroy the UI // use clone otherwise it would modify start and potentially also maxStart, moments are mutable for (const day = start.clone(); day.isSameOrBefore(end, 'day'); day.add(1, 'd')) {