-
Notifications
You must be signed in to change notification settings - Fork 680
Add a support page #9529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add a support page #9529
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
5d63d65
playwright: Set a custom test id
eth3lbert 30a689a
frontend: Add a support route
eth3lbert bed11e3
frontend: Add a crate report form to support page
eth3lbert 892c973
CrateSidebar: Link report button to support page
eth3lbert e85a97d
Footer: Add "Support" link
eth3lbert 23b904a
frontend: Add `PristineParamsService`
eth3lbert ba869e5
Footer: move `Email Support` to the support page
eth3lbert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -130,11 +130,13 @@ | |
| {{/unless}} | ||
| {{/if}} | ||
|
|
||
| <a | ||
| href="mailto:[email protected]?subject=The%20%22{{@crate.name}}%22%20crate&body=I'm%20reporting%20the%20https%3A%2F%2Fcrates.io%2Fcrates%2F{{@crate.name}}%20crate%20because%3A%0A%0A-%20%5B%20%5D%20it%20contains%20spam%0A-%20%5B%20%5D%20it%20is%20name-squatting%20(reserving%20a%20crate%20name%20without%20content)%0A-%20%5B%20%5D%20it%20is%20abusive%20or%20otherwise%20harmful%0A-%20%5B%20%5D%20it%20contains%20a%20vulnerability%20(please%20try%20to%20contact%20the%20crate%20author%20first)%0A-%20%5B%20%5D%20it%20is%20violating%20the%20usage%20policy%20in%20some%20other%20way%20(please%20specify%20below)%0A%0AAdditional%20details%3A%0A%0A%3Cplease%20add%20more%20information%20if%20you%20can%3E" | ||
| <LinkTo | ||
| @route="support" | ||
| @query={{hash inquire="crate-violation" [email protected]}} | ||
| data-test-id="link-crate-report" | ||
| local-class="report-button" | ||
| > | ||
| Report crate | ||
| </a> | ||
| </LinkTo> | ||
| </div> | ||
| </section> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |
| <h1>Get Help</h1> | ||
| <ul role="list"> | ||
| <li><a href="https://doc.rust-lang.org/cargo/">The Cargo Book</a></li> | ||
| <li><a href="mailto:[email protected]">Email Support</a></li> | ||
| <li><LinkTo @route="support" @query={{this.pristineSupportQuery}}>Support</LinkTo></li> | ||
| <li><a href="https://status.crates.io/">System Status</a></li> | ||
| <li><a href="https://github.com/rust-lang/crates.io/issues/new/choose">Report a bug</a></li> | ||
| </ul> | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { inject as service } from '@ember/service'; | ||
| import Component from '@glimmer/component'; | ||
|
|
||
| export default class Footer extends Component { | ||
| @service pristineQuery; | ||
|
|
||
| get pristineSupportQuery() { | ||
| let params = this.pristineQuery.paramsFor('support'); | ||
| return params; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| <form | ||
| local-class="report-form" | ||
| data-testid="crate-report-form" | ||
| ...attributes | ||
| {{on "submit" (prevent-default this.submit)}} | ||
| > | ||
| <h2>Report A Crate</h2> | ||
|
|
||
| <fieldset local-class="form-group" data-test-id="fieldset-crate"> | ||
| {{#let (unique-id) as |id|}} | ||
| <label for={{id}} local-class="form-group-name"> | ||
| Crate | ||
| </label> | ||
| <Input | ||
| id={{id}} | ||
| @type="text" | ||
| @value={{this.crate}} | ||
| autocomplete="off" | ||
| aria-required="true" | ||
| aria-invalid={{if this.crateInvalid "true" "false"}} | ||
| local-class="crate-input" | ||
| data-test-id="crate-input" | ||
| {{auto-focus}} | ||
| {{on "input" this.resetCrateValidation}} | ||
| /> | ||
| {{#if this.crateInvalid}} | ||
| <div local-class="form-group-error" data-test-id="crate-invalid"> | ||
| Please specify a crate. | ||
| </div> | ||
| {{/if}} | ||
| {{/let}} | ||
| </fieldset> | ||
|
|
||
| <fieldset local-class="form-group" data-test-id="fieldset-reasons"> | ||
| <div local-class="form-group-name">Reasons</div> | ||
| <ul role="list" local-class="reasons-list {{if this.reasonsInvalid "invalid"}}"> | ||
| {{#each this.reasons as |option|}} | ||
| <li> | ||
| <label> | ||
| <Input | ||
| @type="checkbox" | ||
| @checked={{this.isReasonSelected option.reason}} | ||
| name={{ option.reason }} | ||
| data-test-id="{{ option.reason }}-checkbox" | ||
| {{on "change" (fn this.toggleReason option.reason)}} | ||
| /> | ||
| {{option.description}} | ||
| </label> | ||
| </li> | ||
| {{/each}} | ||
| </ul> | ||
| {{#if this.reasonsInvalid}} | ||
| <div local-class="form-group-error" data-test-id="reasons-invalid"> | ||
| Please choose reasons to report. | ||
| </div> | ||
| {{/if}} | ||
| </fieldset> | ||
|
|
||
| <fieldset local-class="form-group" data-test-id="fieldset-detail"> | ||
| {{#let (unique-id) as |id|}} | ||
| <label for={{id}} local-class="form-group-name">Detail</label> | ||
| <Textarea | ||
| id={{id}} | ||
| @value={{this.detail}} | ||
| local-class="detail {{if this.detailInvalid "invalid"}}" | ||
| aria-required={{if this.detailInvalid "true" "false" }} | ||
| aria-invalid={{if this.detailInvalid "true" "false"}} | ||
| rows="5" | ||
| data-test-id="detail-input" | ||
| {{on "input" this.resetDetailValidation}} | ||
| /> | ||
| {{#if this.detailInvalid}} | ||
| <div local-class="form-group-error" data-test-id="detail-invalid"> | ||
| Please provide some detail. | ||
| </div> | ||
| {{/if}} | ||
| {{/let}} | ||
| </fieldset> | ||
|
|
||
| <div local-class="buttons"> | ||
| <button | ||
| type="submit" | ||
| local-class="report-button" | ||
| data-test-id="report-button" | ||
| > | ||
| Report | ||
| </button> | ||
| </div> | ||
| </form> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import { action } from '@ember/object'; | ||
| import { inject as service } from '@ember/service'; | ||
| import Component from '@glimmer/component'; | ||
| import { tracked } from '@glimmer/tracking'; | ||
|
|
||
| import window from 'ember-window-mock'; | ||
|
|
||
| const REASONS = [ | ||
| { | ||
| reason: 'spam', | ||
| description: 'it contains spam', | ||
| }, | ||
| { | ||
| reason: 'name-squatting', | ||
| description: 'it is name-squatting (reserving a crate name without content)', | ||
| }, | ||
| { | ||
| reason: 'abuse', | ||
| description: 'it is abusive or otherwise harmful', | ||
| }, | ||
| { | ||
| reason: 'security', | ||
| description: 'it contains a vulnerability (please try to contact the crate author first)', | ||
| }, | ||
| { | ||
| reason: 'other', | ||
| description: 'it is violating the usage policy in some other way (please specify below)', | ||
| }, | ||
| ]; | ||
|
|
||
| export default class CrateReportForm extends Component { | ||
| @service store; | ||
|
|
||
| @tracked crate = ''; | ||
| @tracked selectedReasons = []; | ||
| @tracked detail = ''; | ||
| @tracked crateInvalid = false; | ||
| @tracked reasonsInvalid = false; | ||
| @tracked detailInvalid = false; | ||
|
|
||
| reasons = REASONS; | ||
|
|
||
| constructor() { | ||
| super(...arguments); | ||
| this.crate = this.args.crate; | ||
| } | ||
|
|
||
| validate() { | ||
| this.crateInvalid = !this.crate || !this.crate.trim(); | ||
| this.reasonsInvalid = this.selectedReasons.length === 0; | ||
| this.detailInvalid = this.selectedReasons.includes('other') && !this.detail?.trim(); | ||
| return !this.crateInvalid && !this.reasonsInvalid && !this.detailInvalid; | ||
| } | ||
|
|
||
| @action resetCrateValidation() { | ||
| this.crateInvalid = false; | ||
| } | ||
|
|
||
| @action resetDetailValidation() { | ||
| this.detailInvalid = false; | ||
| } | ||
|
|
||
| @action isReasonSelected(reason) { | ||
| return this.selectedReasons.includes(reason); | ||
| } | ||
|
|
||
| @action toggleReason(reason) { | ||
| this.selectedReasons = this.selectedReasons.includes(reason) | ||
| ? this.selectedReasons.filter(it => it !== reason) | ||
| : [...this.selectedReasons, reason]; | ||
| this.reasonsInvalid = false; | ||
| } | ||
|
|
||
| @action | ||
| submit() { | ||
| if (!this.validate()) { | ||
| return; | ||
| } | ||
|
|
||
| let mailto = this.composeMail(); | ||
| window.open(mailto, '_self'); | ||
| } | ||
|
|
||
| composeMail() { | ||
| let crate = this.crate; | ||
| let reasons = this.reasons | ||
| .map(({ reason, description }) => { | ||
| let selected = this.isReasonSelected(reason); | ||
| return `${selected ? '- [x]' : '- [ ]'} ${description}`; | ||
| }) | ||
| .join('\n'); | ||
| let body = `I'm reporting the https://crates.io/crates/${crate} crate because: | ||
|
|
||
| ${reasons} | ||
|
|
||
| Additional details: | ||
|
|
||
| ${this.detail} | ||
| `; | ||
| let subject = `The "${crate}" crate`; | ||
| let address = '[email protected]'; | ||
| let mailto = `mailto:${address}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; | ||
| return mailto; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| .report-form { | ||
| background-color: var(--main-bg); | ||
| padding: 0.5rem 1rem; | ||
| } | ||
|
|
||
| .form-group { | ||
| border: none; | ||
| margin: 0; | ||
| padding: 0; | ||
|
|
||
| & + & { | ||
| margin-top: 1rem; | ||
| } | ||
| } | ||
|
|
||
| .form-group-name { | ||
| composes: form-group-name from '../../styles/settings/tokens/new.module.css'; | ||
| align-items: center; | ||
| } | ||
|
|
||
| .crate-input { | ||
| composes: name-input from '../../styles/settings/tokens/new.module.css'; | ||
| } | ||
|
|
||
| .reasons-list { | ||
| composes: scopes-list from '../../styles/settings/tokens/new.module.css'; | ||
| label { | ||
| flex-wrap: nowrap; | ||
| } | ||
| input { | ||
| align-self: center; | ||
| } | ||
| } | ||
|
|
||
| .detail { | ||
| padding: var(--space-2xs); | ||
| background-color: light-dark(white, #141413); | ||
| border: 1px solid var(--gray-border); | ||
| border-radius: var(--space-3xs); | ||
| resize: vertical; | ||
| width: 100%; | ||
|
|
||
| &.invalid { | ||
| background: light-dark(#fff2f2, #170808); | ||
| border-color: red; | ||
| } | ||
| } | ||
|
|
||
| .form-group-error { | ||
| composes: form-group-error from '../../styles/settings/tokens/new.module.css'; | ||
| } | ||
|
|
||
| .buttons { | ||
| composes: buttons from '../../styles/settings/tokens/new.module.css'; | ||
| justify-content: end; | ||
| gap: 2rem; | ||
| } | ||
|
|
||
| .button { | ||
| &:focus { | ||
| outline: 1px solid var(--bg-color-top-dark); | ||
| outline-offset: 2px; | ||
| } | ||
| } | ||
|
|
||
| .report-button { | ||
| composes: button; | ||
| composes: button small from '../../styles/shared/buttons.module.css'; | ||
| border-radius: var(--space-3xs); | ||
| } | ||
|
|
||
| .cancel-button { | ||
| composes: button; | ||
| composes: tan-button small from '../../styles/shared/buttons.module.css'; | ||
| border-radius: var(--space-3xs); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import Controller from '@ember/controller'; | ||
| import { tracked } from '@glimmer/tracking'; | ||
|
|
||
| const SUPPORTS = [ | ||
| { | ||
| inquire: 'crate-violation', | ||
| label: 'Report a crate that violates policies', | ||
| }, | ||
| ]; | ||
|
|
||
| const VALID_INQUIRE = new Set(SUPPORTS.map(s => s.inquire)); | ||
|
|
||
| export default class SupportController extends Controller { | ||
| queryParams = ['inquire', 'crate']; | ||
|
|
||
| @tracked inquire; | ||
| @tracked crate; | ||
|
|
||
| supports = SUPPORTS; | ||
|
|
||
| get supported() { | ||
| return VALID_INQUIRE.has(this.inquire); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import Route from '@ember/routing/route'; | ||
|
|
||
| export default class CrateRoute extends Route { | ||
| resetController(controller, isExiting) { | ||
| super.resetController(...arguments); | ||
| // reset queryParams when exiting | ||
| if (isExiting) { | ||
| for (let param of controller.queryParams) { | ||
| controller.set(param, null); | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { getOwner } from '@ember/owner'; | ||
| import Service from '@ember/service'; | ||
|
|
||
| export default class PristineParamsService extends Service { | ||
| paramsFor(route) { | ||
| let params = getOwner(this).lookup(`controller:${route}`)?.queryParams || []; | ||
| return Object.fromEntries(params.map(k => [k, null])); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| .inquire-list { | ||
| display: grid; | ||
| grid-template-columns: 1fr 1fr; | ||
| gap: var(--space-s); | ||
| list-style: none; | ||
| padding: 0; | ||
| } | ||
|
|
||
| .link { | ||
| composes: link from '../components/front-page-list/item.module.css'; | ||
| justify-content: center; | ||
| padding: var(--space-xs) var(--space-s); | ||
| height: inherit; | ||
| min-height: var(--space-2xl); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since this is in a controller I think the property keeps its value across page navigations. in other words: if you press the "report crate" button the property will be set, if you navigate away from the page, then you press the support link in the footer, you will not get to the overview state, but to the "report crate" state again. I'm not 100% sure about this, but we should probably add a test to ensure that navigating to
/supportwithout query params resets the state.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more thing I noticed is that, if you're on the support page with query, if the support link in footer is defined as
<LinkTo @route="support" />it will still inherit those query params. There doesn't seem to be a simple way to avoid this inheritance in the template. You need to use hash to manual overwrite all query params.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if there's a way to test this without
<LinkTo @route="support"/>. We already have<LinkTo @route="support" @query={{hash inquire=null crate=null }}>Support</LinkTo>that reset the query params by setting their values to null.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure about playwright, but in the Ember.js test suite
visit('/support')might be sufficient as it does not reload the whole appThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I just commented out
resetControllerand expected the following to fail, but it didn't 😅 .However, if there's a
<LinkTo @route="support">, I can see that the generated URL has sticky query params.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting, maybe I was wrong then :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured out how to test this in qunit but not playwright. I don't know how to mock the route in playwright QQ.