Skip to content
This repository was archived by the owner on Oct 11, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/mutations/community/updateAdministratorEmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => {
const { id: communityId, email } = args.input;
const { loaders, user } = ctx;

if (!isEmail(email)) {
if (!email || !isEmail(email)) {
return new UserError('Please enter a working email address');
}

Expand Down
2 changes: 1 addition & 1 deletion api/mutations/message/addMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export default requireAuth(async (_: any, args: Input, ctx: GraphQLContext) => {
// at this point we are only dealing with thread messages
const thread = await loaders.thread.load(message.threadId);

if (thread.isDeleted) {
if (!thread || thread.deletedAt) {
trackQueue.add({
userId: user.id,
event: eventFailed,
Expand Down
4 changes: 3 additions & 1 deletion athena/queues/private-channel-request-approved.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export default async (job: Job<PrivateChannelRequestApprovedJobData>) => {
const recipients = await getUsers([userId]);

// only get owners with emails
const filteredRecipients = recipients.filter(user => isEmail(user.email));
const filteredRecipients = recipients.filter(
user => user && isEmail(user.email)
);

// for each owner, create a notification for the app
const usersNotificationPromises = filteredRecipients.map(recipient =>
Expand Down
2 changes: 1 addition & 1 deletion athena/queues/private-channel-request-sent.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default async (job: Job<PrivateChannelRequestJobData>) => {

// only get owners + moderators with emails
const filteredRecipients = recipientsWithUserData.filter(
owner => owner.email && isEmail(owner.email)
owner => owner && owner.email && isEmail(owner.email)
);

// for each owner, create a notification for the app
Expand Down
4 changes: 3 additions & 1 deletion athena/queues/private-community-request-approved.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export default async (job: Job<PrivateCommunityRequestApprovedJobData>) => {

const community = await getCommunityById(communityId);
const recipients = await getUsers([userId]);
const filteredRecipients = recipients.filter(user => isEmail(user.email));
const filteredRecipients = recipients.filter(
user => user && isEmail(user.email)
);
const usersNotificationPromises = filteredRecipients.map(recipient =>
storeUsersNotifications(updatedNotification.id, recipient.id)
);
Expand Down
2 changes: 1 addition & 1 deletion athena/queues/private-community-request-sent.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default async (job: Job<PrivateCommunityRequestJobData>) => {

// only get owners + moderators with emails
const filteredRecipients = recipientsWithUserData.filter(
owner => owner.email && isEmail(owner.email)
owner => owner && owner.email && isEmail(owner.email)
);

// for each owner, create a notification for the app
Expand Down
8 changes: 7 additions & 1 deletion athena/queues/send-slack-invitations.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ const processJob = async (job: Job<SendSlackInvitationsJobData>) => {
// filter out deleted members
.filter(member => !member.deleted)
// only save members with valid email
.filter(member => member.profile.email && isEmail(member.profile.email))
.filter(
member =>
member &&
member.profile &&
member.profile.email &&
isEmail(member.profile.email)
)
// format output data
.map(member => ({
firstName: member.profile.first_name,
Expand Down
43 changes: 42 additions & 1 deletion hermes/send-email.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @flow
import isEmail from 'validator/lib/isEmail';
import postmark from 'postmark';
const debug = require('debug')('hermes:send-email');
const stringify = require('json-stringify-pretty-compact');
Expand Down Expand Up @@ -46,6 +47,35 @@ const sendEmail = (options: Options) => {
});
}

if (!To) {
if (userId) {
trackQueue.add({
userId: userId,
event: events.EMAIL_BOUNCED,
properties: { tag: Tag, error: 'To field was not provided' },
});
}

return;
}

if (!isEmail(To)) {
if (userId) {
trackQueue.add({
userId: userId,
event: events.EMAIL_BOUNCED,
// we can safely log the To field because it's not a valid email, thus not PII
properties: {
tag: Tag,
to: To,
error: 'To field was not a valid email address',
},
});
}

return;
}

// $FlowFixMe
return new Promise((res, rej) => {
client.sendEmailWithTemplate(
Expand All @@ -65,7 +95,7 @@ const sendEmail = (options: Options) => {
trackQueue.add({
userId: userId,
event: events.EMAIL_BOUNCED,
properties: { tag: Tag },
properties: { tag: Tag, error: err.message },
});
}

Expand All @@ -74,6 +104,17 @@ const sendEmail = (options: Options) => {
.catch(e => rej(e));
}

if (err.code === 422) {
if (userId) {
trackQueue.add({
userId: userId,
event: events.EMAIL_BOUNCED,
// we can safely log the To field as error 422 means the To field is malformed anyways and is not a valid email address
properties: { tag: Tag, error: err.message, to: To },
});
}
}

console.error('Error sending email:');
console.error(err);
return rej(err);
Expand Down
2 changes: 1 addition & 1 deletion hyperion/renderer/html-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const getFooter = ({
<script defer="defer" src="/install-raven.js"></script>
<script>window.__SERVER_STATE__=${serialize(state)}</script>
<script>window.__DATA__=${serialize(data)}</script>
<script defer="defer" type="text/javascript" src="https://cdn.polyfill.io/v2/polyfill.min.js"></script>
<script defer="defer" type="text/javascript" src="https://cdn.polyfill.io/v2/polyfill.min.js?features=default,Array.prototype.find,Symbol.iterator"></script>
<script type="text/javascript" src="/static/js/bootstrap.js"></script>
${bundles.map(src => createScriptTag({ src }))}
${createScriptTag({ src: `/static/js/${mainBundle}` })}
Expand Down
2 changes: 1 addition & 1 deletion src/components/chatInput/components/mediaUploader.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class MediaUploader extends React.Component<Props> {

onPaste = (event: any) => {
// Ensure that the image is only pasted if user focuses input
if (!this.props.inputFocused) {
if (!event || !this.props.inputFocused) {
return;
}
const items = (event.clipboardData || event.originalEvent.clipboardData)
Expand Down
2 changes: 1 addition & 1 deletion src/components/composer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ class ComposerWithData extends Component<Props, State> {
const title = e.target.value;
this.persistTitleToLocalStorageWithDebounce(title);
if (/\n$/g.test(title)) {
this.bodyEditor.focus();
this.bodyEditor.focus && this.bodyEditor.focus();
return;
}
this.setState({
Expand Down
2 changes: 1 addition & 1 deletion src/components/threadComposer/components/composer.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ class ThreadComposerWithData extends React.Component<Props, State> {
changeTitle = e => {
const title = e.target.value;
if (/\n$/g.test(title)) {
this.bodyEditor.focus();
this.bodyEditor.focus && this.bodyEditor.focus();
return;
}
persistTitle(title);
Expand Down
4 changes: 3 additions & 1 deletion src/views/community/components/channelList.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ type Props = {
};

class ChannelList extends React.Component<Props> {
sortChannels = (array: Array<any>): Array<any> => {
sortChannels = (array: Array<any>): Array<?any> => {
if (!array || array.length === 0) return [];

const generalChannel = array.find(channel => channel.slug === 'general');
const withoutGeneral = array.filter(channel => channel.slug !== 'general');
const sortedWithoutGeneral = withoutGeneral.sort((a, b) => {
Expand Down
120 changes: 68 additions & 52 deletions src/views/explore/components/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { withRouter } from 'react-router';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import Link from 'src/components/link';
import { Button } from '../../../components/buttons';
import { throttle } from '../../../helpers/utils';
import { Button } from 'src/components/buttons';
import { throttle } from 'src/helpers/utils';
import { searchCommunitiesQuery } from 'shared/graphql/queries/search/searchCommunities';
import type { SearchCommunitiesType } from 'shared/graphql/queries/search/searchCommunities';
import { Spinner } from '../../../components/globals';
import { addToastWithTimeout } from '../../../actions/toasts';
import { Spinner } from 'src/components/globals';
import { addToastWithTimeout } from 'src/actions/toasts';
import OutsideClickHandler from 'src/components/outsideClickHandler';
import {
SearchWrapper,
SearchInput,
Expand Down Expand Up @@ -126,9 +127,7 @@ class Search extends React.Component<Props, State> {
// if person presses escape
if (e.keyCode === 27) {
this.setState({
searchResults: [],
searchIsLoading: false,
searchString: '',
isFocused: false,
});

// $FlowFixMe
Expand All @@ -152,8 +151,11 @@ class Search extends React.Component<Props, State> {
if (indexOfFocusedSearchResult === searchResults.length - 1) return;
if (searchResults.length <= 1) return;

const resultToFocus = searchResults[indexOfFocusedSearchResult + 1];
if (!resultToFocus) return;

return this.setState({
focusedSearchResult: searchResults[indexOfFocusedSearchResult + 1].id,
focusedSearchResult: resultToFocus.id,
});
}

Expand All @@ -162,8 +164,11 @@ class Search extends React.Component<Props, State> {
if (indexOfFocusedSearchResult === 0) return;
if (searchResults.length <= 1) return;

const resultToFocus = searchResults[indexOfFocusedSearchResult - 1];
if (!resultToFocus) return;

return this.setState({
focusedSearchResult: searchResults[indexOfFocusedSearchResult - 1].id,
focusedSearchResult: resultToFocus.id,
});
}
};
Expand Down Expand Up @@ -196,7 +201,7 @@ class Search extends React.Component<Props, State> {
}

onFocus = (e: any) => {
const val = e.target.val;
const val = e.target.value;
if (!val || val.length === 0) return;

const string = val.toLowerCase().trim();
Expand All @@ -209,6 +214,12 @@ class Search extends React.Component<Props, State> {
});
};

hideSearchResults = () => {
return this.setState({
isFocused: false,
});
};

render() {
const {
searchString,
Expand Down Expand Up @@ -241,50 +252,55 @@ class Search extends React.Component<Props, State> {
</SearchInputWrapper>

{// user has typed in a search string
searchString && (
<SearchResultsDropdown>
{searchResults.length > 0 &&
searchResults.map(community => {
return (
<SearchResult
focused={focusedSearchResult === community.id}
key={community.id}
>
<SearchLink to={`/${community.slug}`}>
<SearchResultImage
community={community}
src={community.profilePhoto}
/>
isFocused &&
searchString && (
<OutsideClickHandler onOutsideClick={this.hideSearchResults}>
<SearchResultsDropdown>
{searchResults.length > 0 &&
searchResults.map(community => {
return (
<SearchResult
focused={focusedSearchResult === community.id}
key={community.id}
>
<SearchLink to={`/${community.slug}`}>
<SearchResultImage
community={community}
src={community.profilePhoto}
/>
<SearchResultTextContainer>
<SearchResultMetaWrapper>
<SearchResultName>
{community.name}
</SearchResultName>
{community.metaData && (
<SearchResultMetadata>
{community.metaData.members} members
</SearchResultMetadata>
)}
</SearchResultMetaWrapper>
</SearchResultTextContainer>
</SearchLink>
</SearchResult>
);
})}

{searchResults.length === 0 &&
isFocused && (
<SearchResult>
<SearchResultTextContainer>
<SearchResultMetaWrapper>
<SearchResultName>{community.name}</SearchResultName>
{community.metaData && (
<SearchResultMetadata>
{community.metaData.members} members
</SearchResultMetadata>
)}
</SearchResultMetaWrapper>
<SearchResultNull>
<p>No communities found matching “{searchString}”</p>
<Link to={'/new/community'}>
<Button>Create a Community</Button>
</Link>
</SearchResultNull>
</SearchResultTextContainer>
</SearchLink>
</SearchResult>
);
})}

{searchResults.length === 0 &&
isFocused && (
<SearchResult>
<SearchResultTextContainer>
<SearchResultNull>
<p>No communities found matching “{searchString}”</p>
<Link to={'/new/community'}>
<Button>Create a Community</Button>
</Link>
</SearchResultNull>
</SearchResultTextContainer>
</SearchResult>
)}
</SearchResultsDropdown>
)}
</SearchResult>
)}
</SearchResultsDropdown>
</OutsideClickHandler>
)}
</SearchWrapper>
);
}
Expand Down
Loading