Report a Message in Connect (#12229)
* Frontend Ready for Connect Report Abuse * add feedback api * js defination fix * Added Hooks to the Component * add json response in feedback * Block popup added * fix render issue * Made changes in internal view * change error message * Added few design changes = * add test cases * Update app/javascript/chat/actions/requestActions.js Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * add PR suggestions * add backend test cases * report abuse form close * Update app/javascript/chat/actions/requestActions.js Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Update app/javascript/chat/ReportAbuse/index.jsx Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * add test cases * Update app/javascript/chat/ReportAbuse/index.jsx Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Update app/javascript/chat/ReportAbuse/index.jsx Co-authored-by: Marcy Sutton <holla@marcysutton.com> * Update app/javascript/chat/ReportAbuse/index.jsx Co-authored-by: Marcy Sutton <holla@marcysutton.com> * group the fieldset * fix report abuse api * fix test case * fix request test case * fix typo * cleaned up markup in report abuse component. * Fixed spacing between abuse options. * Fixed wording in report abuse confirmation. * Removed unnecessary data-testid and aria-label attributes. * Added some top margin to the report abuse form. * Update app/javascript/chat/message.jsx Co-authored-by: Suzanne Aitchison <suzanne@forem.com> * Added a legend to the the fieldset. * Update app/javascript/chat/actions/requestActions.js Co-authored-by: Michael Kohl <citizen428@dev.to> Co-authored-by: Sarthak <7lovesharma7@gmail.com> Co-authored-by: Narender Singh <narender2031@gmail.com> Co-authored-by: Marcy Sutton <holla@marcysutton.com> Co-authored-by: Suzanne Aitchison <suzanne@forem.com> Co-authored-by: Michael Kohl <citizen428@dev.to>
This commit is contained in:
parent
ed04875074
commit
86eb75cee7
17 changed files with 461 additions and 37 deletions
|
|
@ -1266,6 +1266,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
.report__abuse-options {
|
||||
.crayons-field + .crayons-field {
|
||||
margin-top: var(--su-2);
|
||||
}
|
||||
}
|
||||
|
||||
.report__abuse__button {
|
||||
width: 135px;
|
||||
}
|
||||
.reported__message {
|
||||
border: 1px solid;
|
||||
max-height: 300px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.membership-section {
|
||||
position: relative;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class FeedbackMessagesController < ApplicationController
|
|||
@feedback_message = FeedbackMessage.new(params)
|
||||
|
||||
recaptcha_enabled = ReCaptcha::CheckEnabled.call(current_user)
|
||||
if (!recaptcha_enabled || recaptcha_verified?) && @feedback_message.save
|
||||
if (!recaptcha_enabled || recaptcha_verified? || connect_feedback?) && @feedback_message.save
|
||||
Slack::Messengers::Feedback.call(
|
||||
user: current_user,
|
||||
type: feedback_message_params[:feedback_type],
|
||||
|
|
@ -20,12 +20,24 @@ class FeedbackMessagesController < ApplicationController
|
|||
)
|
||||
rate_limiter.track_limit_by_action(:feedback_message_creation)
|
||||
|
||||
redirect_to feedback_messages_path
|
||||
respond_to do |format|
|
||||
format.html { redirect_to feedback_messages_path }
|
||||
format.json { render json: { success: true, message: "Your report is submitted" } }
|
||||
end
|
||||
else
|
||||
@previous_message = feedback_message_params[:message]
|
||||
|
||||
flash[:notice] = "Make sure the forms are filled 🤖"
|
||||
render "pages/report_abuse"
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render "pages/report_abuse" }
|
||||
format.json do
|
||||
render json: {
|
||||
success: false,
|
||||
message: @feedback_message.errors_as_sentence,
|
||||
status: :bad_request
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -36,8 +48,12 @@ class FeedbackMessagesController < ApplicationController
|
|||
params["g-recaptcha-response"] && verify_recaptcha(recaptcha_params)
|
||||
end
|
||||
|
||||
def connect_feedback?
|
||||
feedback_message_params[:feedback_type] == "connect"
|
||||
end
|
||||
|
||||
def feedback_message_params
|
||||
allowed_params = %i[message feedback_type category reported_url]
|
||||
allowed_params = %i[message feedback_type category reported_url offender_id]
|
||||
params.require(:feedback_message).permit(allowed_params)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
149
app/javascript/chat/ReportAbuse/index.jsx
Normal file
149
app/javascript/chat/ReportAbuse/index.jsx
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import { h, Fragment } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useState } from 'preact/hooks';
|
||||
import { reportAbuse, blockUser } from '../actions/requestActions';
|
||||
import { addSnackbarItem } from '../../Snackbar';
|
||||
import { Button, FormField, RadioButton } from '@crayons';
|
||||
|
||||
/**
|
||||
* This component render the report abuse
|
||||
*
|
||||
* @param {object} props
|
||||
* @param {object} props.data
|
||||
* @param {function} props.closeReportAbuseForm
|
||||
*
|
||||
* @component
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* <ReportAbuse
|
||||
* data={data}
|
||||
* closeReportAbuseForm={closeReportAbuseForm}
|
||||
* />
|
||||
*
|
||||
*/
|
||||
function ReportAbuse({ data, closeReportAbuseForm }) {
|
||||
const [category, setCategory] = useState(null);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setCategory(e.target.value);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const response = await reportAbuse(
|
||||
data.message,
|
||||
'connect',
|
||||
category,
|
||||
data.user_id,
|
||||
);
|
||||
const { success, message } = response;
|
||||
if (success) {
|
||||
const confirmBlock = window.confirm(
|
||||
`The message will be reported.\n\nWould you like to block this person as well?\n\nThis will:
|
||||
- prevent them from commenting on your posts
|
||||
- block all notifications from them
|
||||
- prevent them from messaging you via chat`,
|
||||
);
|
||||
|
||||
if (confirmBlock) {
|
||||
const response = await blockUser(data.user_id);
|
||||
if (response.result === 'blocked') {
|
||||
addSnackbarItem({
|
||||
message:
|
||||
'Your report has been submitted and the user has been blocked',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
addSnackbarItem({ message: 'Your report has been submitted.' });
|
||||
}
|
||||
closeReportAbuseForm();
|
||||
} else {
|
||||
addSnackbarItem({ message });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<section className="mt-7 p-4 grid gap-2 crayons-card mb-4">
|
||||
<h1 className="lh-tight mb-4 mt-0">Report Abuse</h1>
|
||||
<p>
|
||||
Thank you for reporting any abuse that violates our{' '}
|
||||
<a href="/code-of-conduct">code of conduct</a> or{' '}
|
||||
<a href="/terms">terms and conditions</a>. We continue to try to make
|
||||
this environment a great one for everybody.
|
||||
</p>
|
||||
<fieldset className="report__abuse-options p-4 justify-between">
|
||||
<legend>Why is this content inappropriate?</legend>
|
||||
<FormField variant="radio">
|
||||
<RadioButton
|
||||
id="rude_or_vulgar"
|
||||
name="rude_or_vulgar"
|
||||
value="rude or vulgar"
|
||||
checked={category === 'rude or vulgar'}
|
||||
onClick={handleChange}
|
||||
/>
|
||||
<label htmlFor="rude_or_vulgar" className="crayons-field__label">
|
||||
Rude or vulgar
|
||||
</label>
|
||||
</FormField>
|
||||
<FormField variant="radio">
|
||||
<RadioButton
|
||||
id="harassment"
|
||||
name="harassment"
|
||||
value="harassment"
|
||||
checked={category === 'harassment'}
|
||||
onClick={handleChange}
|
||||
/>
|
||||
<label htmlFor="harassment" className="crayons-field__label">
|
||||
Harassment or hate speech
|
||||
</label>
|
||||
</FormField>
|
||||
<FormField variant="radio">
|
||||
<RadioButton
|
||||
id="spam"
|
||||
name="spam"
|
||||
value="spam"
|
||||
checked={category === 'spam'}
|
||||
onClick={handleChange}
|
||||
/>
|
||||
<label htmlFor="spam" className="crayons-field__label">
|
||||
Spam or copyright issue
|
||||
</label>
|
||||
</FormField>
|
||||
<FormField variant="radio">
|
||||
<RadioButton
|
||||
id="listings"
|
||||
name="listings"
|
||||
value="listings"
|
||||
checked={category === 'listings'}
|
||||
onClick={handleChange}
|
||||
/>
|
||||
<label htmlFor="listings" className="crayons-field__label">
|
||||
Inappropriate listings message/category
|
||||
</label>
|
||||
</FormField>
|
||||
<h2>Message to Report</h2>
|
||||
<div
|
||||
className="reported__message p-2 mt-2 mb-3"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: data.message }}
|
||||
/>
|
||||
<Button disabled={category === null} size="s" onClick={handleSubmit}>
|
||||
Report Message
|
||||
</Button>
|
||||
</fieldset>
|
||||
</section>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
ReportAbuse.propTypes = {
|
||||
resource: PropTypes.shape({
|
||||
data: PropTypes.shape({
|
||||
user_id: PropTypes.number.isRequired,
|
||||
message: PropTypes.element.isRequired,
|
||||
}),
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default ReportAbuse;
|
||||
44
app/javascript/chat/__tests__/reportAbuse.test.jsx
Normal file
44
app/javascript/chat/__tests__/reportAbuse.test.jsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { h } from 'preact';
|
||||
import { render } from '@testing-library/preact';
|
||||
import { axe } from 'jest-axe';
|
||||
import ReportAbuse from '../ReportAbuse';
|
||||
|
||||
describe('<ReportAbuse />', () => {
|
||||
it('should have no a11y violations', async () => {
|
||||
const { container } = render(
|
||||
<ReportAbuse
|
||||
data={{
|
||||
message: 'HI',
|
||||
user_id: 1,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
const results = await axe(container);
|
||||
|
||||
expect(results).toHaveNoViolations();
|
||||
});
|
||||
|
||||
it('should render the component', () => {
|
||||
const { getByText, getByLabelText } = render(
|
||||
<ReportAbuse data={{ message: 'HI', user_id: 1 }} />,
|
||||
);
|
||||
|
||||
expect(getByText('Report Abuse')).toBeDefined();
|
||||
|
||||
const vulgarInput = getByLabelText('Rude or vulgar');
|
||||
expect(vulgarInput.value).toEqual('rude or vulgar');
|
||||
|
||||
const harassmentInput = getByLabelText('Harassment or hate speech');
|
||||
expect(harassmentInput.value).toEqual('harassment');
|
||||
|
||||
const listingsInput = getByLabelText(
|
||||
'Inappropriate listings message/category',
|
||||
);
|
||||
expect(listingsInput.value).toEqual('listings');
|
||||
|
||||
const spamInput = getByLabelText('Spam or copyright issue');
|
||||
expect(spamInput.value).toEqual('spam');
|
||||
|
||||
expect(getByText('Report Message')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -92,3 +92,52 @@ export async function updateMembership(membershipId, userAction) {
|
|||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} feedback_message
|
||||
* @param {string} type_of_feedback
|
||||
* @param {string} category
|
||||
* @param {string} reported_url
|
||||
*/
|
||||
export async function reportAbuse(
|
||||
feedback_message,
|
||||
feedback_type,
|
||||
category,
|
||||
offender_id,
|
||||
) {
|
||||
const response = await request('/feedback_messages', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
feedback_message: {
|
||||
message: feedback_message,
|
||||
feedback_type,
|
||||
category,
|
||||
offender_id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks a user with the given ID from using Connect
|
||||
*
|
||||
* @param {number} userId
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
export async function blockUser(userId) {
|
||||
const response = await request('/user_blocks', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
user_block: {
|
||||
blocked_id: userId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1073,6 +1073,15 @@ export default class Chat extends Component {
|
|||
}));
|
||||
};
|
||||
|
||||
closeReportAbuseForm = () => {
|
||||
const { activeChannelId } = this.state;
|
||||
this.setActiveContentState(activeChannelId, null);
|
||||
this.setState({
|
||||
fullscreenContent: null,
|
||||
expanded: window.innerWidth > NARROW_WIDTH_LIMIT,
|
||||
});
|
||||
};
|
||||
|
||||
setActiveContent = (response) => {
|
||||
const { activeChannelId } = this.state;
|
||||
this.setActiveContentState(activeChannelId, response);
|
||||
|
|
@ -1184,7 +1193,6 @@ export default class Chat extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
return messages[activeChannelId].map((message) =>
|
||||
message.action ? (
|
||||
<ActionMessage
|
||||
|
|
@ -1209,11 +1217,21 @@ export default class Chat extends Component {
|
|||
onContentTrigger={this.triggerActiveContent}
|
||||
onDeleteMessageTrigger={this.triggerDeleteMessage}
|
||||
onEditMessageTrigger={this.triggerEditMessage}
|
||||
onReportMessageTrigger={this.triggerReportMessage}
|
||||
/>
|
||||
),
|
||||
);
|
||||
};
|
||||
triggerReportMessage = (messageId) => {
|
||||
const { activeChannelId, messages } = this.state;
|
||||
|
||||
this.setActiveContent({
|
||||
data: messages[activeChannelId].find(
|
||||
(message) => message.id === messageId,
|
||||
),
|
||||
type_of: 'message-report-abuse',
|
||||
});
|
||||
};
|
||||
triggerChannelFilter = (e) => {
|
||||
const { channelTypeFilter } = this.state;
|
||||
const filters =
|
||||
|
|
@ -1594,6 +1612,7 @@ export default class Chat extends Component {
|
|||
resource={state.activeContent[state.activeChannelId]}
|
||||
activeChannel={state.activeChannel}
|
||||
fullscreen={state.fullscreenContent === 'sidecar'}
|
||||
closeReportAbuseForm={this.closeReportAbuseForm}
|
||||
/>
|
||||
<VideoContent
|
||||
videoPath={state.videoPath}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import ChannelRequest from './channelRequest';
|
|||
import RequestManager from './RequestManager/RequestManager';
|
||||
import ChatChannelSettings from './ChatChannelSettings/ChatChannelSettings';
|
||||
import Draw from './draw';
|
||||
import ReportAbuse from './ReportAbuse';
|
||||
|
||||
const smartSvgIcon = (content, d) => (
|
||||
<svg
|
||||
|
|
@ -33,13 +34,20 @@ export default class Content extends Component {
|
|||
fullscreen: PropTypes.bool.isRequired,
|
||||
onTriggerContent: PropTypes.func.isRequired,
|
||||
updateRequestCount: PropTypes.func.isRequired,
|
||||
closeReportAbuseForm: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onTriggerContent, fullscreen, resource } = this.props;
|
||||
const {
|
||||
onTriggerContent,
|
||||
fullscreen,
|
||||
resource,
|
||||
closeReportAbuseForm,
|
||||
} = this.props;
|
||||
if (!resource) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
// TODO: A button (role="button") cannot contain other interactive elements, i.e. buttons.
|
||||
// TODO: These should have key click events as well.
|
||||
|
|
@ -80,13 +88,16 @@ export default class Content extends Component {
|
|||
'M20 3h2v6h-2V5h-4V3h4zM4 3h4v2H4v4H2V3h2zm16 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z',
|
||||
)}
|
||||
</button>
|
||||
<Display resource={resource} />
|
||||
<Display
|
||||
resource={resource}
|
||||
closeReportAbuseForm={closeReportAbuseForm}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Display = ({ resource }) => {
|
||||
function Display({ resource, closeReportAbuseForm }) {
|
||||
switch (resource.type_of) {
|
||||
case 'loading-user':
|
||||
return <div className="loading-user" title="Loading user" />;
|
||||
|
|
@ -116,7 +127,14 @@ const Display = ({ resource }) => {
|
|||
handleLeavingChannel={resource.handleLeavingChannel}
|
||||
/>
|
||||
);
|
||||
case 'message-report-abuse':
|
||||
return (
|
||||
<ReportAbuse
|
||||
data={resource.data}
|
||||
closeReportAbuseForm={closeReportAbuseForm}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const Message = ({
|
|||
profileImageUrl,
|
||||
onContentTrigger,
|
||||
onDeleteMessageTrigger,
|
||||
onReportMessageTrigger,
|
||||
onEditMessageTrigger,
|
||||
}) => {
|
||||
const spanStyle = { color };
|
||||
|
|
@ -60,6 +61,22 @@ const Message = ({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
const dropdownReport = (
|
||||
<div className="message__actions">
|
||||
<span className="ellipsis__menubutton">
|
||||
<img src={ThreeDotsIcon} alt="message actions" />
|
||||
</span>
|
||||
|
||||
<div className="messagebody__dropdownmenu report__abuse__button">
|
||||
<Button
|
||||
variant="ghost-danger"
|
||||
onClick={(_) => onReportMessageTrigger(id)}
|
||||
>
|
||||
Report Abuse
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="chatmessage">
|
||||
|
|
@ -119,7 +136,7 @@ const Message = ({
|
|||
' '
|
||||
)}
|
||||
</div>
|
||||
{userID === currentUserId ? dropdown : ' '}
|
||||
{userID === currentUserId ? dropdown : dropdownReport}
|
||||
</div>
|
||||
<div className="chatmessage__bodytext">
|
||||
<MessageArea />
|
||||
|
|
@ -143,6 +160,7 @@ Message.propTypes = {
|
|||
onContentTrigger: PropTypes.func.isRequired,
|
||||
onDeleteMessageTrigger: PropTypes.func.isRequired,
|
||||
onEditMessageTrigger: PropTypes.func.isRequired,
|
||||
onReportMessageTrigger: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Message.defaultProps = {
|
||||
|
|
|
|||
|
|
@ -128,7 +128,11 @@ class FollowUsers extends Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<button type="button" class="crayons-btn crayons-btn--ghost-brand -ml-2" onClick={() => this.handleSelectAll()}>
|
||||
<button
|
||||
type="button"
|
||||
class="crayons-btn crayons-btn--ghost-brand -ml-2"
|
||||
onClick={() => this.handleSelectAll()}
|
||||
>
|
||||
{followText}
|
||||
</button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const menus = [...document.getElementsByClassName('js-hamburger-trigger')];
|
|||
const moreMenus = [...document.getElementsByClassName('js-nav-more-trigger')];
|
||||
|
||||
getInstantClick().then((spa) => {
|
||||
spa.on('change', function () {
|
||||
spa.on('change', () => {
|
||||
const { currentPage } = document.getElementById('page-content').dataset;
|
||||
|
||||
setCurrentPageIconLink(currentPage, getPageEntries());
|
||||
|
|
|
|||
|
|
@ -53,9 +53,7 @@ function renderTagsFollowed(user = userData()) {
|
|||
}
|
||||
|
||||
function renderSidebar() {
|
||||
const sidebarContainer = document.getElementById(
|
||||
'sidebar-wrapper-right',
|
||||
);
|
||||
const sidebarContainer = document.getElementById('sidebar-wrapper-right');
|
||||
|
||||
// If the screen's width is less than 1024px we don't need this extra data.
|
||||
if (sidebarContainer && screen.width > 1023 && window.location.pathname === '/') {
|
||||
|
|
@ -67,11 +65,10 @@ function renderSidebar() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const feedTimeFrame = frontPageFeedPathNames.get(window.location.pathname);
|
||||
|
||||
if (!document.getElementById('featured-story-marker')) {
|
||||
const waitingForDataLoad = setInterval(function dataLoadedCheck() {
|
||||
const waitingForDataLoad = setInterval(() => {
|
||||
const { user = null, userStatus } = document.body.dataset;
|
||||
if (userStatus === 'logged-out') {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,11 @@
|
|||
<div class="row">
|
||||
<div class="col-9">
|
||||
<h5 class="fw-bold">
|
||||
<% if feedback_message.feedback_type == "connect" %>
|
||||
Reporter and Affected:
|
||||
<% else %>
|
||||
Reporter:
|
||||
<% end %>
|
||||
</h5>
|
||||
<h5>
|
||||
<% if feedback_message.reporter_id? %>
|
||||
|
|
@ -29,19 +33,35 @@
|
|||
Anonymous
|
||||
<% end %>
|
||||
</h5>
|
||||
|
||||
<% if feedback_message.feedback_type == "connect" %>
|
||||
<h5 class="fw-bold">
|
||||
Offender:
|
||||
</h5>
|
||||
<h5>
|
||||
<%= feedback_message.offender.name %>
|
||||
<a href="<%= feedback_message.offender.path %>">@<%= feedback_message.offender.username %></a>
|
||||
</h5>
|
||||
<% else %>
|
||||
<h5 class="fw-bold">
|
||||
Reported URL (new tab):
|
||||
</h5>
|
||||
<h5>
|
||||
<a href="<%= feedback_message.reported_url %>" target="_blank" rel="noopener"><%= feedback_message.reported_url %></a>
|
||||
</h5>
|
||||
Reported URL (new tab):
|
||||
</h5>
|
||||
<h5>
|
||||
<a href="<%= feedback_message.reported_url %>" target="_blank" rel="noopener"><%= feedback_message.reported_url %></a>
|
||||
</h5>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<h3>
|
||||
<h3 class="report__tags">
|
||||
<span class="badge badge-warning float-right"><%= feedback_message.category.titleize %></span>
|
||||
<% if feedback_message.feedback_type == "connect" %>
|
||||
<span class="badge badge-warning float-right badge-connect"><%= feedback_message.feedback_type %></span>
|
||||
<% end %>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<% if feedback_message.feedback_type != "connect" %>
|
||||
<h5 class="fw-bold">
|
||||
Message:
|
||||
</h5>
|
||||
|
|
@ -52,6 +72,14 @@
|
|||
<%= feedback_message.message %>
|
||||
<% end %>
|
||||
</p>
|
||||
<% else %>
|
||||
<h5 class="fw-bold">
|
||||
Message from Offender:
|
||||
</h5>
|
||||
<div class="reported__message">
|
||||
<%= raw(feedback_message.message) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
|
|
|
|||
|
|
@ -22,4 +22,21 @@
|
|||
color: black;
|
||||
text-align: center;
|
||||
}
|
||||
.badge-connect {
|
||||
background-color: #26d9ca;
|
||||
margin-top: 5px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.reported__message {
|
||||
border: 1px solid;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
max-height: 300px;
|
||||
overflow: scroll;
|
||||
}
|
||||
.report__tags{
|
||||
display: grid;
|
||||
justify-items: end;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<a href="<%= readinglist_path %>" class="crayons-link crayons-link--block">Reading list</a>
|
||||
<a href="<%= user_settings_path %>" class="crayons-link crayons-link--block">Settings</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="p-1">
|
||||
<a href="<%= signout_confirm_path %>" class="crayons-link crayons-link--block" id="last-nav-link">Sign Out</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,19 +4,22 @@ We use the [Field Test](https://github.com/ankane/field_test) gem for conducting
|
|||
simple A/B tests.
|
||||
|
||||
If you want to propose an A/B test of a feature, you may make a pull request
|
||||
which defines the hypotheses and what admins should look for to declare a winner.
|
||||
As A/B tests are going to have results that may differ from Forem to Forem, the
|
||||
process is relatively immature. In the future we may have more studies that can return anonymous ecosystem-wide results.
|
||||
which defines the hypotheses and what admins should look for to declare a
|
||||
winner. As A/B tests are going to have results that may differ from Forem to
|
||||
Forem, the process is relatively immature. In the future we may have more
|
||||
studies that can return anonymous ecosystem-wide results.
|
||||
|
||||
A/B tests are inherently the most useful in _large_ Forems, where qualitative
|
||||
feedback may be more useful on small Forems. As such, [DEV](https://dev.to)
|
||||
is our largest Forem and therefore can provide us with the most feedback for
|
||||
our existing A/B tests. However, we must keep in mind that DEV results may
|
||||
not apply well to future large Forems. We should seek to re-run useful experiments within the ecosystem after time has passed.
|
||||
feedback may be more useful on small Forems. As such, [DEV](https://dev.to) is
|
||||
our largest Forem and therefore can provide us with the most feedback for our
|
||||
existing A/B tests. However, we must keep in mind that DEV results may not apply
|
||||
well to future large Forems. We should seek to re-run useful experiments within
|
||||
the ecosystem after time has passed.
|
||||
|
||||
## Creating a new A/B test
|
||||
|
||||
Follow the guidelines of the field test gem and add the test info to [config/field_test.yml](https://github.com/forem/forem/blob/master/config/field_test.yml).
|
||||
Follow the guidelines of the field test gem and add the test info to
|
||||
[config/field_test.yml](https://github.com/forem/forem/blob/master/config/field_test.yml).
|
||||
|
||||
Then where you want to trigger the variant, you'll add some code like this:
|
||||
|
||||
|
|
@ -38,9 +41,9 @@ Then where you want to trigger the variant, you'll add some code like this:
|
|||
end
|
||||
```
|
||||
|
||||
Which would find or create the test variant for that user in particular. If
|
||||
this code is not called in the controller or view, you'll need to first
|
||||
include the gem helpers at the top of the file...
|
||||
Which would find or create the test variant for that user in particular. If this
|
||||
code is not called in the controller or view, you'll need to first include the
|
||||
gem helpers at the top of the file...
|
||||
|
||||
```
|
||||
include FieldTest::Helpers
|
||||
|
|
@ -53,4 +56,5 @@ To record a successful field test outcome, you should call something like this
|
|||
.perform_async(user_id, :follow_implicit_points, "user_creates_reaction")
|
||||
```
|
||||
|
||||
And modify that class as needed to determine whether to record the successful trial.
|
||||
And modify that class as needed to determine whether to record the successful
|
||||
trial.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "feedback_messages", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
describe "POST /feedback_messages" do
|
||||
def mock_recaptcha_verification
|
||||
# rubocop:disable RSpec/AnyInstance
|
||||
|
|
@ -49,6 +51,26 @@ RSpec.describe "feedback_messages", type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context "when feedback is created by chat" do
|
||||
before do
|
||||
sign_in user
|
||||
post "/feedback_messages", params: {
|
||||
feedback_message: {
|
||||
message: "Test Message",
|
||||
feedback_type: "connect",
|
||||
category: "rude or vulgar",
|
||||
offender_id: user.id
|
||||
}
|
||||
}, as: :json
|
||||
end
|
||||
|
||||
it "creates a feedback message" do
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body["success"]).to eq(true)
|
||||
expect(FeedbackMessage.where(offender_id: user.id).count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "with valid params and recaptcha not configured" do
|
||||
before do
|
||||
allow(SiteConfig).to receive(:recaptcha_secret_key).and_return(nil)
|
||||
|
|
|
|||
24
spec/system/feedback_message_spec.rb
Normal file
24
spec/system/feedback_message_spec.rb
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Feedback report by chat channel messages", type: :system do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context "when user create a report abuse feedback message" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "feedback messahe should increase by one", js: true do
|
||||
expect do
|
||||
post "/feedback_messages", params: {
|
||||
feedback_message: {
|
||||
message: "Test Message",
|
||||
feedback_type: "connect",
|
||||
category: "rude or vulgar",
|
||||
offender_id: user.id
|
||||
}
|
||||
}, as: :json
|
||||
end.to change(FeedbackMessage, :count).by(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue