From e52904ae2b9699f013c474f08d082484408ebe32 Mon Sep 17 00:00:00 2001 From: Alexandre Amoedo Amorim Date: Fri, 22 Apr 2022 17:56:21 -0300 Subject: [PATCH] Collect user data and integrate error page with Intercom This commit puts an ErrorBoundary around each Route's parent component. This way each ErrorBoundary is under the IntlProvider and has Intercom in scope so we integrate and prefill support request messages when crashes happen. ErrorBoundary component now gets a `component` prop to specify which component it is wrapping and this will be sent with the errbit report. It is also collecting user name, dbid and email to send with the report, to help gain more insight/context on the crashes. Note I: The @airbrake-js/browser calls to the Errbit server seem to ignore the extra data added as context, params and session, unless the error is a string or we cast a new instance from the raised error object. To be investigated. See github issue: https://github.com/airbrake/airbrake-js/issues/1193 Note II: MainErrorBoundary has been renamed to ErrorBoundary Fixes: CHECK-1744 --- src/app/components/Root.js | 16 +- src/app/components/UserConfirmPage.js | 155 ++++++------ src/app/components/UserPasswordChange.js | 107 +++++---- src/app/components/UserPasswordReset.js | 149 ++++++------ src/app/components/error/MainErrorBoundary.js | 55 ----- src/app/components/media/MediaComponent.js | 3 + src/app/components/media/MediaPage.js | 29 ++- src/app/components/media/MediaSource.js | 74 +++--- src/app/components/media/MediaTasks.js | 29 ++- .../components/media/ReportDesigner/index.js | 15 +- src/app/components/project/Project.js | 33 +-- src/app/components/project/ProjectGroup.js | 163 ++++++------- src/app/components/search/AllItems.js | 23 +- src/app/components/search/SavedSearch.js | 153 ++++++------ src/app/components/source/Me.js | 19 +- src/app/components/source/User.js | 19 +- src/app/components/team/ImportedReports.js | 23 +- src/app/components/team/SuggestedMatches.js | 87 +++---- src/app/components/team/Team.js | 29 ++- src/app/components/team/Teams.js | 15 +- src/app/components/team/TiplineInbox.js | 101 ++++---- src/app/components/team/Trash.js | 23 +- src/app/components/trends/Trends.js | 131 +++++----- src/app/components/trends/TrendsItem.js | 225 +++++++++--------- src/app/helpers.js | 8 +- 25 files changed, 850 insertions(+), 834 deletions(-) delete mode 100644 src/app/components/error/MainErrorBoundary.js diff --git a/src/app/components/Root.js b/src/app/components/Root.js index 793e54d0a2..1737d2ddac 100644 --- a/src/app/components/Root.js +++ b/src/app/components/Root.js @@ -11,7 +11,7 @@ import NotFound from './NotFound'; import UserConfirmPage from './UserConfirmPage'; import UserPasswordChange from './UserPasswordChange'; import UserPasswordReset from './UserPasswordReset'; -import MainErrorBoundary from './error/MainErrorBoundary'; +import ErrorBoundary from './error/ErrorBoundary'; import User from './source/User'; import Me from './source/Me'; import Team from './team/Team'; @@ -55,10 +55,10 @@ class Root extends Component { window.Check = { store }; return ( - - - - + + + + @@ -106,9 +106,9 @@ class Root extends Component { - - - + + + ); } } diff --git a/src/app/components/UserConfirmPage.js b/src/app/components/UserConfirmPage.js index dd9bc61a92..35384e2cdb 100644 --- a/src/app/components/UserConfirmPage.js +++ b/src/app/components/UserConfirmPage.js @@ -10,6 +10,7 @@ import Box from '@material-ui/core/Box'; import PageTitle from './PageTitle'; import { FormattedGlobalMessage } from './MappedMessage'; import CheckAgreeTerms from './CheckAgreeTerms'; +import ErrorBoundary from './error/ErrorBoundary'; import globalStrings from '../globalStrings'; import { stringHelper } from '../customHelpers'; import { @@ -46,83 +47,85 @@ function UserConfirmPage({ params }) { const classes = useStyles(); return ( - - - - - - - - - {appNameHuman => ( - {appNameHuman} - )} - - - { params.confirmType === 'confirmed' ? - () : null - } - { params.confirmType === 'already-confirmed' ? - () : null - } - - - { params.confirmType === 'confirmed' ? - () : null - } - { params.confirmType === 'already-confirmed' ? - () : null - } - { params.confirmType === 'unconfirmed' ? - () : null - } - - - - - - - - - + + + + + - - - + + + + {appNameHuman => ( + {appNameHuman} + )} + + + { params.confirmType === 'confirmed' ? + () : null + } + { params.confirmType === 'already-confirmed' ? + () : null + } + + + { params.confirmType === 'confirmed' ? + () : null + } + { params.confirmType === 'already-confirmed' ? + () : null + } + { params.confirmType === 'unconfirmed' ? + () : null + } + + + + + + + + + + + + + + ); } diff --git a/src/app/components/UserPasswordChange.js b/src/app/components/UserPasswordChange.js index b10af5d2ee..617eba36d7 100644 --- a/src/app/components/UserPasswordChange.js +++ b/src/app/components/UserPasswordChange.js @@ -10,6 +10,7 @@ import PageTitle from './PageTitle'; import ChangePasswordComponent from './ChangePasswordComponent'; import { FormattedGlobalMessage } from './MappedMessage'; import CheckAgreeTerms from './CheckAgreeTerms'; +import ErrorBoundary from './error/ErrorBoundary'; import globalStrings from '../globalStrings'; import { stringHelper } from '../customHelpers'; import { @@ -46,60 +47,62 @@ function UserPasswordChange() { const classes = useStyles(); return ( - - - - - - - - {appNameHuman => ( - {appNameHuman} - )} - - + + + + + + + + + {appNameHuman => ( + {appNameHuman} + )} + + + { showConfirmDialog ? + + : + } + + { showConfirmDialog ? - - : + + + + + + + + : +
+ + + +
} -
- - { showConfirmDialog ? - - - - - - - - : -
- - - -
- } -
- - - -
-
+ + + + + + + ); } diff --git a/src/app/components/UserPasswordReset.js b/src/app/components/UserPasswordReset.js index 2c12240b71..969f81efa5 100644 --- a/src/app/components/UserPasswordReset.js +++ b/src/app/components/UserPasswordReset.js @@ -15,6 +15,7 @@ import PageTitle from './PageTitle'; import { FormattedGlobalMessage } from './MappedMessage'; import CheckAgreeTerms from './CheckAgreeTerms'; import GenericUnknownErrorMessage from './GenericUnknownErrorMessage'; +import ErrorBoundary from './error/ErrorBoundary'; import globalStrings from '../globalStrings'; import { stringHelper } from '../customHelpers'; import { getErrorMessageForRelayModernProblem } from '../helpers'; @@ -108,80 +109,82 @@ const UserPasswordReset = (props) => { const classes = useStyles(); return ( - - - - - - - - {appNameHuman => ( - {appNameHuman} - )} - - - { pagetitleMessage } - - { showConfirmDialog ? [ - - - , - - - , - ] : [ - - { previousErrorMsg ?

{previousErrorMsg}

: null } - -
- } - onChange={handleChange} - helperText={errorMsg} - error={errorMsg} - variant="outlined" - margin="normal" - fullWidth - autoFocus - /> -
-
, - - - - , - ]} -
- - + + + + -
-
+ + + + {appNameHuman => ( + {appNameHuman} + )} + + + { pagetitleMessage } + + { showConfirmDialog ? [ + + + , + + + , + ] : [ + + { previousErrorMsg ?

{previousErrorMsg}

: null } + +
+ } + onChange={handleChange} + helperText={errorMsg} + error={errorMsg} + variant="outlined" + margin="normal" + fullWidth + autoFocus + /> +
+
, + + + + , + ]} +
+ + + +
+ + ); }; diff --git a/src/app/components/error/MainErrorBoundary.js b/src/app/components/error/MainErrorBoundary.js deleted file mode 100644 index a9218d096e..0000000000 --- a/src/app/components/error/MainErrorBoundary.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { Notifier } from '@airbrake/browser'; -import config from 'config'; // eslint-disable-line require-path-exists/exists -import ErrorPage from './ErrorPage'; -import GenericUnknownErrorMessage from '../GenericUnknownErrorMessage'; - -class MainErrorBoundary extends React.Component { - constructor(props) { - super(props); - this.state = { hasError: false }; - if (config.errbitApiKey && config.errbitHost) { - this.airbrake = new Notifier({ - host: config.errbitHost, - projectId: 1, - projectKey: config.errbitApiKey, - }); - } - } - - static getDerivedStateFromError() { - return { hasError: true }; - } - - componentDidCatch(error, errorInfo) { - if (this.airbrake) { - this.airbrake.notify({ - error, - params: { info: errorInfo }, - }); - } - } - - render() { - if (this.state.hasError) { - return ( - - } - cardText={} - /> - ); - } - - return this.props.children; - } -} - -export default MainErrorBoundary; diff --git a/src/app/components/media/MediaComponent.js b/src/app/components/media/MediaComponent.js index 75f75921f2..62ede0eaf4 100644 --- a/src/app/components/media/MediaComponent.js +++ b/src/app/components/media/MediaComponent.js @@ -294,6 +294,9 @@ class MediaComponent extends Component { media.quote = media.media.quote; media.embed_path = media.media.embed_path; + // console.log(this.props.trololo.meajude.xocxoc); // eslint-disable-line no-console + console.log(JSON.parse('bli')); // eslint-disable-line no-console + const { playerState: { start, diff --git a/src/app/components/media/MediaPage.js b/src/app/components/media/MediaPage.js index 3ab93e2de3..3097327594 100644 --- a/src/app/components/media/MediaPage.js +++ b/src/app/components/media/MediaPage.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import MediaPageLayout from './MediaPageLayout'; +import ErrorBoundary from '../error/ErrorBoundary'; import { getListUrlQueryAndIndex } from '../../urlHelpers'; import NotFound from '../NotFound'; @@ -26,19 +27,21 @@ export default function MediaPage({ route, routeParams, location }) { } return ( - + + + ); } diff --git a/src/app/components/media/MediaSource.js b/src/app/components/media/MediaSource.js index a34c2b42ba..fd7d5104fd 100644 --- a/src/app/components/media/MediaSource.js +++ b/src/app/components/media/MediaSource.js @@ -4,6 +4,7 @@ import { QueryRenderer, graphql, commitMutation } from 'react-relay/compat'; import { makeStyles } from '@material-ui/core/styles'; import MediasLoading from './MediasLoading'; import ChangeMediaSource from './ChangeMediaSource'; +import ErrorBoundary from '../error/ErrorBoundary'; import SourceInfo from '../source/SourceInfo'; import CreateMediaSource from './CreateMediaSource'; import { can } from '../Can'; @@ -14,7 +15,6 @@ const useStyles = makeStyles(theme => ({ }, })); - // Auto-resize the iframe when embedded in the browser extension let sourceTabFrameTimer = null; @@ -161,47 +161,49 @@ const MediaSource = ({ projectMedia, params }) => { } return ( - + { + if (!error && !props) { + return ; } - } - `} - variables={{ - ids, - teamSlug, - }} - render={({ error, props }) => { - if (!error && !props) { - return ; - } - if (!error && props) { - return ; - } + if (!error && props) { + return ; + } - // TODO: We need a better error handling in the future, standardized with other components - return null; - }} - /> + // TODO: We need a better error handling in the future, standardized with other components + return null; + }} + /> + ); }; diff --git a/src/app/components/media/MediaTasks.js b/src/app/components/media/MediaTasks.js index 97646a73ff..a903c2908b 100644 --- a/src/app/components/media/MediaTasks.js +++ b/src/app/components/media/MediaTasks.js @@ -13,6 +13,7 @@ import styled from 'styled-components'; import MediaLanguageChip from './MediaLanguageChip'; import MediasLoading from './MediasLoading'; import MediaTags from './MediaTags'; +import ErrorBoundary from '../error/ErrorBoundary'; import Task from '../task/Task'; import Tasks from '../task/Tasks'; import CreateTask from '../task/CreateTask'; @@ -375,22 +376,26 @@ const MediaTasks = (props) => { if (fieldset === 'metadata') { return ( - } - route={route} - renderLoading={() => } - /> + + } + route={route} + renderLoading={() => } + /> + ); } return ( - } - route={route} - renderLoading={() => } - /> + + } + route={route} + renderLoading={() => } + /> + ); }; diff --git a/src/app/components/media/ReportDesigner/index.js b/src/app/components/media/ReportDesigner/index.js index 8a23915466..1cc893c9a7 100644 --- a/src/app/components/media/ReportDesigner/index.js +++ b/src/app/components/media/ReportDesigner/index.js @@ -1,6 +1,7 @@ import React from 'react'; import Relay from 'react-relay/classic'; import PropTypes from 'prop-types'; +import ErrorBoundary from '../../error/ErrorBoundary'; import RelayContainer from '../../../relay/RelayContainer'; import MediaRoute from '../../../relay/MediaRoute'; import ReportDesignerComponent from './ReportDesignerComponent'; @@ -67,12 +68,14 @@ const ReportDesigner = (props) => { const route = new MediaRoute({ ids }); return ( - } - forceFetch - /> + + } + forceFetch + /> + ); }; diff --git a/src/app/components/project/Project.js b/src/app/components/project/Project.js index 71c1427c37..efa928c079 100644 --- a/src/app/components/project/Project.js +++ b/src/app/components/project/Project.js @@ -6,6 +6,7 @@ import { graphql } from 'react-relay/compat'; import { FormattedMessage } from 'react-intl'; import FolderOpenIcon from '@material-ui/icons/FolderOpen'; import VisibilityIcon from '@material-ui/icons/Visibility'; +import ErrorBoundary from '../error/ErrorBoundary'; import ProjectRoute from '../../relay/ProjectRoute'; import CheckContext from '../../CheckContext'; import MediasLoading from '../media/MediasLoading'; @@ -200,21 +201,23 @@ const ProjectContainer = Relay.createContainer(ProjectComponent, { const Project = ({ routeParams, ...props }) => { const route = new ProjectRoute({ projectId: routeParams.projectId }); return ( - ( - /* TODO make GraphQL Projects query filter by Team - * ... in the meantime, we can fake an error by showing "Not Found" when the - * Project exists but is in a different Team than the one the user asked for - * in the URL. - */ - (data.project && data.project.team && data.project.team.slug !== routeParams.team) - ? - : - )} - renderLoading={() => } - /> + + ( + /* TODO make GraphQL Projects query filter by Team + * ... in the meantime, we can fake an error by showing "Not Found" when the + * Project exists but is in a different Team than the one the user asked for + * in the URL. + */ + (data.project && data.project.team && data.project.team.slug !== routeParams.team) + ? + : + )} + renderLoading={() => } + /> + ); }; diff --git a/src/app/components/project/ProjectGroup.js b/src/app/components/project/ProjectGroup.js index fb27e88cf1..2014c519de 100644 --- a/src/app/components/project/ProjectGroup.js +++ b/src/app/components/project/ProjectGroup.js @@ -4,103 +4,106 @@ import { QueryRenderer, graphql } from 'react-relay/compat'; import Relay from 'react-relay/classic'; import { FormattedMessage } from 'react-intl'; import FolderSpecialIcon from '@material-ui/icons/FolderSpecial'; +import ErrorBoundary from '../error/ErrorBoundary'; import Search from '../search/Search'; import { safelyParseJSON } from '../../helpers'; import ProjectActions from '../drawer/Projects/ProjectActions'; const ProjectGroup = ({ routeParams }) => ( - + { - if (!error && props) { - const query = { - ...safelyParseJSON(routeParams.query, {}), - project_group_id: [props.project_group.dbid], - }; + `} + variables={{ + id: routeParams.projectGroupId, + }} + render={({ error, props }) => { + if (!error && props) { + const query = { + ...safelyParseJSON(routeParams.query, {}), + project_group_id: [props.project_group.dbid], + }; - return ( -
- } - title={props.project_group.title} - listDescription={props.project_group.description} - listActions={ - } - updateMutation={graphql` - mutation ProjectGroupUpdateProjectGroupMutation($input: UpdateProjectGroupInput!) { - updateProjectGroup(input: $input) { - project_group { - id - title - description + return ( +
+ } + title={props.project_group.title} + listDescription={props.project_group.description} + listActions={ + } + updateMutation={graphql` + mutation ProjectGroupUpdateProjectGroupMutation($input: UpdateProjectGroupInput!) { + updateProjectGroup(input: $input) { + project_group { + id + title + description + } } } + `} + deleteMessage={ + } - `} - deleteMessage={ - - } - deleteMutation={graphql` - mutation ProjectGroupDestroyProjectGroupMutation($input: DestroyProjectGroupInput!) { - destroyProjectGroup(input: $input) { - deletedId - team { - id - projects(first: 10000) { - edges { - node { - id - dbid - title - medias_count - project_group_id + deleteMutation={graphql` + mutation ProjectGroupDestroyProjectGroupMutation($input: DestroyProjectGroupInput!) { + destroyProjectGroup(input: $input) { + deletedId + team { + id + projects(first: 10000) { + edges { + node { + id + dbid + title + medias_count + project_group_id + } } } } } } - } - `} - /> - } - teamSlug={routeParams.team} - projectGroup={props.project_group} - query={query} - hideFields={['project_group_id', 'country', 'cluster_teams', 'cluster_published_reports']} - page="collection" - /> -
- ); - } - return null; - }} - /> + `} + /> + } + teamSlug={routeParams.team} + projectGroup={props.project_group} + query={query} + hideFields={['project_group_id', 'country', 'cluster_teams', 'cluster_published_reports']} + page="collection" + /> +
+ ); + } + return null; + }} + /> + ); ProjectGroup.propTypes = { diff --git a/src/app/components/search/AllItems.js b/src/app/components/search/AllItems.js index f2cb7cc335..6bab560636 100644 --- a/src/app/components/search/AllItems.js +++ b/src/app/components/search/AllItems.js @@ -2,20 +2,23 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import Search from './Search'; +import ErrorBoundary from '../error/ErrorBoundary'; import { safelyParseJSON } from '../../helpers'; export default function AllItems({ routeParams }) { return ( - } - query={safelyParseJSON(routeParams.query, {})} - teamSlug={routeParams.team} - hideFields={[ - 'country', 'cluster_teams', 'cluster_published_reports', - ]} - /> + + } + query={safelyParseJSON(routeParams.query, {})} + teamSlug={routeParams.team} + hideFields={[ + 'country', 'cluster_teams', 'cluster_published_reports', + ]} + /> + ); } AllItems.propTypes = { diff --git a/src/app/components/search/SavedSearch.js b/src/app/components/search/SavedSearch.js index 1c378ff073..605ec5ddee 100644 --- a/src/app/components/search/SavedSearch.js +++ b/src/app/components/search/SavedSearch.js @@ -4,95 +4,98 @@ import { QueryRenderer, graphql } from 'react-relay/compat'; import Relay from 'react-relay/classic'; import { FormattedMessage } from 'react-intl'; import ListIcon from '@material-ui/icons/List'; +import ErrorBoundary from '../error/ErrorBoundary'; import Search from '../search/Search'; import { safelyParseJSON } from '../../helpers'; import ProjectActions from '../drawer/Projects/ProjectActions'; const SavedSearch = ({ routeParams }) => ( - + { - if (!error && props) { - // Weird Relay error happens here if "filters" is a JSON object instead of a JSON string... - // "Uncaught TypeError: Cannot assign to read only property '' of object '#'" - const savedQuery = props.saved_search.filters || '{}'; - const query = routeParams.query ? - safelyParseJSON(routeParams.query, {}) : - safelyParseJSON(savedQuery, {}); + `} + variables={{ + id: routeParams.savedSearchId, + }} + render={({ error, props }) => { + if (!error && props) { + // Weird Relay error happens here if "filters" is a JSON object instead of a JSON string... + // "Uncaught TypeError: Cannot assign to read only property '' of object '#'" + const savedQuery = props.saved_search.filters || '{}'; + const query = routeParams.query ? + safelyParseJSON(routeParams.query, {}) : + safelyParseJSON(savedQuery, {}); - return ( -
- } - listActions={ - } - updateMutation={graphql` - mutation SavedSearchUpdateSavedSearchMutation($input: UpdateSavedSearchInput!) { - updateSavedSearch(input: $input) { - saved_search { - id - title + return ( +
+ } + listActions={ + } + updateMutation={graphql` + mutation SavedSearchUpdateSavedSearchMutation($input: UpdateSavedSearchInput!) { + updateSavedSearch(input: $input) { + saved_search { + id + title + } } } + `} + deleteMessage={ + } - `} - deleteMessage={ - - } - deleteMutation={graphql` - mutation SavedSearchDestroySavedSearchMutation($input: DestroySavedSearchInput!) { - destroySavedSearch(input: $input) { - deletedId - team { - id + deleteMutation={graphql` + mutation SavedSearchDestroySavedSearchMutation($input: DestroySavedSearchInput!) { + destroySavedSearch(input: $input) { + deletedId + team { + id + } } } - } - `} - /> - } - title={props.saved_search.title} - teamSlug={routeParams.team} - query={query} - savedSearch={props.saved_search} - hideFields={['country', 'cluster_teams', 'cluster_published_reports']} - page="list" - /> -
- ); - } - return null; - }} - /> + `} + /> + } + title={props.saved_search.title} + teamSlug={routeParams.team} + query={query} + savedSearch={props.saved_search} + hideFields={['country', 'cluster_teams', 'cluster_published_reports']} + page="list" + /> +
+ ); + } + return null; + }} + /> + ); SavedSearch.propTypes = { diff --git a/src/app/components/source/Me.js b/src/app/components/source/Me.js index d4c5318335..48beacf2d9 100644 --- a/src/app/components/source/Me.js +++ b/src/app/components/source/Me.js @@ -1,9 +1,10 @@ import React from 'react'; import Relay from 'react-relay/classic'; -import MeRoute from '../../relay/MeRoute'; import UserComponent from './UserComponent'; -import userFragment from '../../relay/userFragment'; +import ErrorBoundary from '../error/ErrorBoundary'; import MediasLoading from '../media/MediasLoading'; +import MeRoute from '../../relay/MeRoute'; +import userFragment from '../../relay/userFragment'; const MeContainer = Relay.createContainer(UserComponent, { fragments: { @@ -14,12 +15,14 @@ const MeContainer = Relay.createContainer(UserComponent, { const Me = (props) => { const route = new MeRoute(); return ( - } - renderFetched={data => } - /> + + } + renderFetched={data => } + /> + ); }; diff --git a/src/app/components/source/User.js b/src/app/components/source/User.js index 5983781fb2..04612456ae 100644 --- a/src/app/components/source/User.js +++ b/src/app/components/source/User.js @@ -1,9 +1,10 @@ import React from 'react'; import Relay from 'react-relay/classic'; -import UserRoute from '../../relay/UserRoute'; import UserComponent from './UserComponent'; -import userFragment from '../../relay/userFragment'; +import ErrorBoundary from '../error/ErrorBoundary'; import MediasLoading from '../media/MediasLoading'; +import UserRoute from '../../relay/UserRoute'; +import userFragment from '../../relay/userFragment'; const UserContainer = Relay.createContainer(UserComponent, { fragments: { @@ -14,12 +15,14 @@ const UserContainer = Relay.createContainer(UserComponent, { const User = (props) => { const route = new UserRoute({ userId: props.params.userId }); return ( - } - renderFetched={data => } - /> + + } + renderFetched={data => } + /> + ); }; diff --git a/src/app/components/team/ImportedReports.js b/src/app/components/team/ImportedReports.js index b51e144a42..15e6fe10ee 100644 --- a/src/app/components/team/ImportedReports.js +++ b/src/app/components/team/ImportedReports.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import GetAppIcon from '@material-ui/icons/GetApp'; +import ErrorBoundary from '../error/ErrorBoundary'; import { safelyParseJSON } from '../../helpers'; import Search from '../search/Search'; import CheckChannels from '../../CheckChannels'; @@ -13,16 +14,18 @@ export default function ImportedReports({ routeParams }) { }; return ( - } - icon={} - teamSlug={routeParams.team} - query={query} - hideFields={['channels', 'country', 'cluster_teams', 'cluster_published_reports']} - page="imported-reports" - /> + + } + icon={} + teamSlug={routeParams.team} + query={query} + hideFields={['channels', 'country', 'cluster_teams', 'cluster_published_reports']} + page="imported-reports" + /> + ); } ImportedReports.propTypes = { diff --git a/src/app/components/team/SuggestedMatches.js b/src/app/components/team/SuggestedMatches.js index fac3914e1b..c62842d400 100644 --- a/src/app/components/team/SuggestedMatches.js +++ b/src/app/components/team/SuggestedMatches.js @@ -4,54 +4,57 @@ import { QueryRenderer, graphql } from 'react-relay/compat'; import Relay from 'react-relay/classic'; import { FormattedMessage } from 'react-intl'; import NewReleasesIcon from '@material-ui/icons/NewReleases'; +import ErrorBoundary from '../error/ErrorBoundary'; import { safelyParseJSON } from '../../helpers'; import Search from '../search/Search'; const SuggestedMatches = ({ routeParams }) => ( - + { - if (!error && props) { - const { team } = props; - const savedQuery = team.get_suggested_matches_filters || {}; - let query = {}; - if (typeof routeParams.query === 'undefined' && Object.keys(savedQuery).length > 0) { - query = { ...savedQuery }; - } else { - query = { - suggestions_count: { min: 1 }, - sort: 'suggestions_count', - sort_type: 'DESC', - ...safelyParseJSON(routeParams.query, {}), - }; + `} + variables={{ + slug: routeParams.team, + }} + render={({ error, props }) => { + if (!error && props) { + const { team } = props; + const savedQuery = team.get_suggested_matches_filters || {}; + let query = {}; + if (typeof routeParams.query === 'undefined' && Object.keys(savedQuery).length > 0) { + query = { ...savedQuery }; + } else { + query = { + suggestions_count: { min: 1 }, + sort: 'suggestions_count', + sort_type: 'DESC', + ...safelyParseJSON(routeParams.query, {}), + }; + } + return ( + } + icon={} + teamSlug={routeParams.team} + query={query} + hideFields={['suggestions_count', 'country', 'cluster_teams', 'cluster_published_reports']} + page="suggested-matches" + /> + ); } - return ( - } - icon={} - teamSlug={routeParams.team} - query={query} - hideFields={['suggestions_count', 'country', 'cluster_teams', 'cluster_published_reports']} - page="suggested-matches" - /> - ); - } - return null; - }} - /> + return null; + }} + /> + ); SuggestedMatches.propTypes = { diff --git a/src/app/components/team/Team.js b/src/app/components/team/Team.js index 96f1ccd47d..c4763cab31 100644 --- a/src/app/components/team/Team.js +++ b/src/app/components/team/Team.js @@ -2,6 +2,7 @@ import React from 'react'; import { QueryRenderer, graphql } from 'react-relay/compat'; import Relay from 'react-relay/classic'; import TeamComponent from './TeamComponent'; +import ErrorBoundary from '../error/ErrorBoundary'; const renderQuery = ({ error, props }, { route, params }) => { if (!error && props) { @@ -20,20 +21,22 @@ const Team = (props) => { } return ( - + renderQuery(data, props)} - /> + `} + variables={{ + teamSlug, + }} + render={data => renderQuery(data, props)} + /> + ); }; diff --git a/src/app/components/team/Teams.js b/src/app/components/team/Teams.js index aa8d1b1037..a7e3eeb389 100644 --- a/src/app/components/team/Teams.js +++ b/src/app/components/team/Teams.js @@ -2,15 +2,18 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; import SwitchTeams from './SwitchTeams'; import PageTitle from '../PageTitle'; +import ErrorBoundary from '../error/ErrorBoundary'; import { ContentColumn } from '../../styles/js/shared'; const Teams = () => ( - - } /> - - - - + + + } /> + + + + + ); export default Teams; diff --git a/src/app/components/team/TiplineInbox.js b/src/app/components/team/TiplineInbox.js index eea6c848b3..793db282ed 100644 --- a/src/app/components/team/TiplineInbox.js +++ b/src/app/components/team/TiplineInbox.js @@ -4,62 +4,65 @@ import { QueryRenderer, graphql } from 'react-relay/compat'; import Relay from 'react-relay/classic'; import { FormattedMessage } from 'react-intl'; import ForumIcon from '@material-ui/icons/Forum'; +import ErrorBoundary from '../error/ErrorBoundary'; import { safelyParseJSON } from '../../helpers'; import Search from '../search/Search'; import CheckChannels from '../../CheckChannels'; const TiplineInbox = ({ routeParams }) => ( - + { - if (!error && props) { - const { team } = props; - const defaultStatusId = team.verification_statuses.default; - const savedQuery = team.get_tipline_inbox_filters || {}; - let query = {}; - if (typeof routeParams.query === 'undefined' && Object.keys(savedQuery).length > 0) { - query = { ...savedQuery }; - } else { - query = typeof routeParams.query === 'undefined' ? - { - read: ['0'], - projects: ['-1'], - verification_status: [defaultStatusId], - ...safelyParseJSON(routeParams.query, {}), - } : - { - ...safelyParseJSON(routeParams.query, {}), - }; + `} + variables={{ + slug: routeParams.team, + }} + render={({ error, props }) => { + if (!error && props) { + const { team } = props; + const defaultStatusId = team.verification_statuses.default; + const savedQuery = team.get_tipline_inbox_filters || {}; + let query = {}; + if (typeof routeParams.query === 'undefined' && Object.keys(savedQuery).length > 0) { + query = { ...savedQuery }; + } else { + query = typeof routeParams.query === 'undefined' ? + { + read: ['0'], + projects: ['-1'], + verification_status: [defaultStatusId], + ...safelyParseJSON(routeParams.query, {}), + } : + { + ...safelyParseJSON(routeParams.query, {}), + }; + } + query.channels = [CheckChannels.ANYTIPLINE]; + return ( + } + icon={} + teamSlug={routeParams.team} + query={query} + hideFields={['channels', 'country', 'cluster_teams', 'cluster_published_reports']} + page="tipline-inbox" + /> + ); } - query.channels = [CheckChannels.ANYTIPLINE]; - return ( - } - icon={} - teamSlug={routeParams.team} - query={query} - hideFields={['channels', 'country', 'cluster_teams', 'cluster_published_reports']} - page="tipline-inbox" - /> - ); - } - return null; - }} - /> + return null; + }} + /> + ); TiplineInbox.propTypes = { diff --git a/src/app/components/team/Trash.js b/src/app/components/team/Trash.js index e43335a520..8e8dd6542f 100644 --- a/src/app/components/team/Trash.js +++ b/src/app/components/team/Trash.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import DeleteIcon from '@material-ui/icons/Delete'; +import ErrorBoundary from '../error/ErrorBoundary'; import { safelyParseJSON } from '../../helpers'; import Search from '../search/Search'; import CheckArchivedFlags from '../../CheckArchivedFlags'; @@ -17,16 +18,18 @@ export default function Trash({ routeParams }) { }; return ( - } - icon={} - teamSlug={routeParams.team} - query={query} - hideFields={['user', 'country', 'cluster_teams', 'cluster_published_reports', 'archived']} - page="trash" - /> + + } + icon={} + teamSlug={routeParams.team} + query={query} + hideFields={['user', 'country', 'cluster_teams', 'cluster_published_reports', 'archived']} + page="trash" + /> + ); } Trash.propTypes = { diff --git a/src/app/components/trends/Trends.js b/src/app/components/trends/Trends.js index bf1beb44af..b4cbcaa8e4 100644 --- a/src/app/components/trends/Trends.js +++ b/src/app/components/trends/Trends.js @@ -5,76 +5,79 @@ import Relay from 'react-relay/classic'; import { FormattedMessage } from 'react-intl'; import { browserHistory } from 'react-router'; import { TrendingUp as TrendingUpIcon } from '@material-ui/icons'; +import ErrorBoundary from '../error/ErrorBoundary'; import Search from '../search/Search'; import { safelyParseJSON } from '../../helpers'; const Trends = ({ routeParams }) => ( - + { - if (!error && props) { - const { team } = props; - if (!team.get_trends_enabled) { - browserHistory.push('/check/not-found'); + `} + variables={{ + slug: routeParams.team, + }} + render={({ error, props }) => { + if (!error && props) { + const { team } = props; + if (!team.get_trends_enabled) { + browserHistory.push('/check/not-found'); + } + const savedQuery = team.get_trends_filters || {}; + let query = {}; + if (typeof routeParams.query === 'undefined' && Object.keys(savedQuery).length > 0) { + query = { ...savedQuery }; + } else { + query = { + sort: 'cluster_last_item_at', + ...safelyParseJSON(routeParams.query, {}), + }; + } + query.trends = true; + query.country = true; + return ( + } + icon={} + query={query} + teamSlug={routeParams.team} + showExpand + resultType="trends" + hideFields={[ + 'folder', + 'projects', + 'project_group_id', + 'tags', + 'read', + 'verification_status', + 'users', + 'assigned_to', + 'published_by', + 'team_tasks', + 'channels', + 'linked_items_count', + 'suggestions_count', + 'demand', + 'sources', + 'dynamic', + ]} + /> + ); } - const savedQuery = team.get_trends_filters || {}; - let query = {}; - if (typeof routeParams.query === 'undefined' && Object.keys(savedQuery).length > 0) { - query = { ...savedQuery }; - } else { - query = { - sort: 'cluster_last_item_at', - ...safelyParseJSON(routeParams.query, {}), - }; - } - query.trends = true; - query.country = true; - return ( - } - icon={} - query={query} - teamSlug={routeParams.team} - showExpand - resultType="trends" - hideFields={[ - 'folder', - 'projects', - 'project_group_id', - 'tags', - 'read', - 'verification_status', - 'users', - 'assigned_to', - 'published_by', - 'team_tasks', - 'channels', - 'linked_items_count', - 'suggestions_count', - 'demand', - 'sources', - 'dynamic', - ]} - /> - ); - } - return null; - }} - /> + return null; + }} + /> + ); Trends.propTypes = { diff --git a/src/app/components/trends/TrendsItem.js b/src/app/components/trends/TrendsItem.js index d0495e3da1..3355344ec5 100644 --- a/src/app/components/trends/TrendsItem.js +++ b/src/app/components/trends/TrendsItem.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { QueryRenderer, graphql } from 'react-relay/compat'; import Relay from 'react-relay/classic'; import TrendsItemComponent from './TrendsItemComponent'; +import ErrorBoundary from '../error/ErrorBoundary'; import { getListUrlQueryAndIndex } from '../../urlHelpers'; const TrendsItem = ({ routeParams, location }) => { @@ -23,142 +24,144 @@ const TrendsItem = ({ routeParams, location }) => { }; return ( - + + `} + variables={{ + clusterId: `${routeParams.clusterId}`, + }} + render={renderQuery} + /> + ); }; diff --git a/src/app/helpers.js b/src/app/helpers.js index a6984cd9bb..e709e5256d 100644 --- a/src/app/helpers.js +++ b/src/app/helpers.js @@ -64,8 +64,12 @@ function getStatusStyle(status, property) { * Truncate a string and append ellipsis. */ function truncateLength(str, length = 70) { - const dots = str.length > length ? '...' : ''; - return `${str.substring(0, length)}${dots}`; + if (typeof str === 'string') { + const dots = str.length > length ? '...' : ''; + return `${str.substring(0, length)}${dots}`; + } + + return str; } /**