diff --git a/client/src/www/ui_components/app-root.js b/client/src/www/ui_components/app-root.js index dae3997229..d16ea06a55 100644 --- a/client/src/www/ui_components/app-root.js +++ b/client/src/www/ui_components/app-root.js @@ -352,7 +352,6 @@ export class AppRoot extends mixinBehaviors([AppLocalizeBehavior], PolymerElemen name="contact" id="contactView" localize="[[localize]]" - variant="client" error-reporter="[[errorReporter]]" on-success="showContactSuccessToast" on-error="showContactErrorToast" diff --git a/client/src/www/views/contact_view/app_type.ts b/client/src/www/views/contact_view/app_type.ts deleted file mode 100644 index 7329dd21fb..0000000000 --- a/client/src/www/views/contact_view/app_type.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright 2023 The Outline Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** The app variant to use, which dictates the options available to the user. */ -export enum AppType { - CLIENT = 'client', - MANAGER = 'manager', -} diff --git a/client/src/www/views/contact_view/index.spec.ts b/client/src/www/views/contact_view/index.spec.ts index 387f72b55a..95217c6361 100644 --- a/client/src/www/views/contact_view/index.spec.ts +++ b/client/src/www/views/contact_view/index.spec.ts @@ -25,7 +25,7 @@ import {OutlineErrorReporter, SentryErrorReporter} from '../../shared/error_repo import {localize} from '../../testing/localize'; -describe('ContactView client variant', () => { +describe('ContactView', () => { let el: ContactView; let mockErrorReporter: jasmine.SpyObj; @@ -35,7 +35,7 @@ describe('ContactView client variant', () => { Object.getOwnPropertyNames(SentryErrorReporter.prototype) ); el = await fixture( - html` ` + html` ` ); }); @@ -198,39 +198,3 @@ describe('ContactView client variant', () => { }); }); }); - -describe('ContactView manager variant', () => { - let el: ContactView; - - describe('when the user selects that they have no open tickets', () => { - let issueSelector: Select; - - beforeEach(async () => { - const mockErrorReporter: jasmine.SpyObj = jasmine.createSpyObj( - 'SentryErrorReporter', - Object.getOwnPropertyNames(SentryErrorReporter.prototype) - ); - el = await fixture( - html` - - ` - ); - - const radioButton = el.shadowRoot!.querySelectorAll('mwc-formfield mwc-radio')[1] as HTMLElement; - radioButton.click(); - await nextFrame(); - - issueSelector = el.shadowRoot!.querySelector('mwc-select')!; - }); - - it('shows the issue selector', () => { - expect(issueSelector.hasAttribute('hidden')).toBeFalse(); - }); - - it('shows the correct items in the selector', () => { - const issueItemEls = issueSelector.querySelectorAll('mwc-list-item'); - const issueTypes = Array.from(issueItemEls).map((el: ListItemBase) => el.value); - expect(issueTypes).toEqual(['cannot-add-server', 'connection', 'managing', 'general']); - }); - }); -}); diff --git a/client/src/www/views/contact_view/index.ts b/client/src/www/views/contact_view/index.ts index d287b611cd..9db36c0e54 100644 --- a/client/src/www/views/contact_view/index.ts +++ b/client/src/www/views/contact_view/index.ts @@ -29,18 +29,32 @@ import {Ref, createRef, ref} from 'lit/directives/ref.js'; import {unsafeHTML} from 'lit/directives/unsafe-html.js'; import './support_form'; -import {AppType} from './app_type'; -import {IssueType, UNSUPPORTED_ISSUE_TYPE_HELPPAGES} from './issue_type'; import {FormValues, SupportForm, ValidFormValues} from './support_form'; import {OutlineErrorReporter} from '../../shared/error_reporter'; /** The possible steps in the stepper. Only one step is shown at a time. */ -enum Step { +enum ProgressStep { ISSUE_WIZARD, // Step to ask for their specific issue. FORM, // The contact form. EXIT, // Final message to show, if any. } +/** Supported issue types in the feedback flow. */ +enum IssueType { + NO_SERVER = 'no-server', + CANNOT_ADD_SERVER = 'cannot-add-server', + CONNECTION = 'connection', + PERFORMANCE = 'performance', + GENERAL = 'general', +} + +/** A map of unsupported issue types to helppage URLs to redirect users to. */ +const UNSUPPORTED_ISSUE_TYPE_HELPPAGES = new Map([ + [IssueType.NO_SERVER, 'https://support.getoutline.org/s/article/How-do-I-get-an-access-key'], + [IssueType.CANNOT_ADD_SERVER, 'https://support.getoutline.org/s/article/What-if-my-access-key-doesn-t-work'], + [IssueType.CONNECTION, 'https://support.getoutline.org/s/article/Why-can-t-I-connect-to-the-Outline-service'], +]); + @customElement('contact-view') export class ContactView extends LitElement { static styles = [ @@ -67,11 +81,6 @@ export class ContactView extends LitElement { transform: translate(-50%, -50%); } - h1 { - font-size: 1rem; - margin-bottom: var(--contact-view-gutter, var(--outline-gutter)); - } - p { margin-top: .25rem; } @@ -123,22 +132,18 @@ export class ContactView extends LitElement { `, ]; - private static readonly ISSUES: {[key in AppType]: IssueType[]} = { - [AppType.CLIENT]: [ - IssueType.NO_SERVER, - IssueType.CANNOT_ADD_SERVER, - IssueType.CONNECTION, - IssueType.PERFORMANCE, - IssueType.GENERAL, - ], - [AppType.MANAGER]: [IssueType.CANNOT_ADD_SERVER, IssueType.CONNECTION, IssueType.MANAGING, IssueType.GENERAL], - }; + private static readonly ISSUES: IssueType[] = [ + IssueType.NO_SERVER, + IssueType.CANNOT_ADD_SERVER, + IssueType.CONNECTION, + IssueType.PERFORMANCE, + IssueType.GENERAL, + ]; @property({type: Function}) localize: Localizer = msg => msg; - @property({type: String}) variant: AppType = AppType.CLIENT; @property({type: Object, attribute: 'error-reporter'}) errorReporter: OutlineErrorReporter; - @state() private step: Step = Step.ISSUE_WIZARD; + @state() private currentStep: ProgressStep = ProgressStep.ISSUE_WIZARD; private selectedIssueType?: IssueType; private exitTemplate?: TemplateResult; @@ -169,14 +174,14 @@ export class ContactView extends LitElement { const hasOpenTicket = radio.value; if (hasOpenTicket) { this.exitTemplate = html`${this.localize('contact-view-exit-open-ticket')}`; - this.step = Step.EXIT; + this.currentStep = ProgressStep.EXIT; return; } this.showIssueSelector = true; } private selectIssue(e: SingleSelectedEvent) { - this.selectedIssueType = ContactView.ISSUES[this.variant][e.detail.index]; + this.selectedIssueType = ContactView.ISSUES[e.detail.index]; if (UNSUPPORTED_ISSUE_TYPE_HELPPAGES.has(this.selectedIssueType)) { // TODO: Send users to localized support pages based on chosen language. @@ -184,11 +189,11 @@ export class ContactView extends LitElement { `contact-view-exit-${this.selectedIssueType}`, UNSUPPORTED_ISSUE_TYPE_HELPPAGES.get(this.selectedIssueType) ); - this.step = Step.EXIT; + this.currentStep = ProgressStep.EXIT; return; } - this.step = Step.FORM; + this.currentStep = ProgressStep.FORM; } reset() { @@ -198,7 +203,7 @@ export class ContactView extends LitElement { if (!element.ref.value) return; element.ref.value.checked = false; }); - this.step = Step.ISSUE_WIZARD; + this.currentStep = ProgressStep.ISSUE_WIZARD; this.formValues = {}; } @@ -248,7 +253,6 @@ export class ContactView extends LitElement { ${this.exitTemplate}

`; } - case Step.ISSUE_WIZARD: + case ProgressStep.ISSUE_WIZARD: default: { return html` ${this.renderIntroTemplate} @@ -299,7 +303,7 @@ export class ContactView extends LitElement { ?fixedMenuPosition=${true} @selected="${this.selectIssue}" > - ${ContactView.ISSUES[this.variant].map(value => { + ${ContactView.ISSUES.map(value => { return html` ${this.localize(`contact-view-issue-${value}`)} diff --git a/client/src/www/views/contact_view/issue_type.ts b/client/src/www/views/contact_view/issue_type.ts deleted file mode 100644 index 19281d6500..0000000000 --- a/client/src/www/views/contact_view/issue_type.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2023 The Outline Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** Supported issue types in the feedback flow. */ -export enum IssueType { - NO_SERVER = 'no-server', - CANNOT_ADD_SERVER = 'cannot-add-server', - CONNECTION = 'connection', - MANAGING = 'managing', - PERFORMANCE = 'performance', - GENERAL = 'general', -} - -/** A map of unsupported issue types to helppage URLs to redirect users to. */ -export const UNSUPPORTED_ISSUE_TYPE_HELPPAGES = new Map([ - [IssueType.NO_SERVER, 'https://support.getoutline.org/s/article/How-do-I-get-an-access-key'], - [IssueType.CANNOT_ADD_SERVER, 'https://support.getoutline.org/s/article/What-if-my-access-key-doesn-t-work'], - [IssueType.CONNECTION, 'https://support.getoutline.org/s/article/Why-can-t-I-connect-to-the-Outline-service'], -]); diff --git a/client/src/www/views/contact_view/stories.ts b/client/src/www/views/contact_view/stories.ts index 2a33a58fab..5eaac173d6 100644 --- a/client/src/www/views/contact_view/stories.ts +++ b/client/src/www/views/contact_view/stories.ts @@ -19,32 +19,21 @@ import {html} from 'lit'; import './index'; -import {AppType} from './app_type'; import {localize} from '../../testing/localize'; export default { title: 'Contact View', component: 'contact-view', argTypes: { - variant: { - description: 'Style variant of the contact view.', - defaultValue: AppType.CLIENT, - options: Object.values(AppType), - control: { - type: 'radio', - defaultValue: AppType.CLIENT, - }, - }, onSuccess: {action: 'success'}, onError: {action: 'error'}, }, }; -export const Example = ({variant, onSuccess, onError}: {variant: AppType; onSuccess: Function; onError: Function}) => +export const Example = ({onSuccess, onError}: {onSuccess: Function; onError: Function}) => html` { expect(el).toBeInstanceOf(SupportForm); }); - it('shows correct fields for the client variant', async () => { - const el = await fixture(html` `); - - expect(el.shadowRoot!.querySelector('mwc-textfield[name="accessKeySource"]')).not.toBeNull(); - expect(el.shadowRoot!.querySelector('mwc-select[name="cloudProvider"]')).toBeNull(); - }); - - it('shows correct fields for the manager variant', async () => { - const el = await fixture(html` `); - - expect(el.shadowRoot!.querySelector('mwc-textfield[name="accessKeySource"]')).toBeNull(); - expect(el.shadowRoot!.querySelector('mwc-select[name="cloudProvider"]')).not.toBeNull(); - }); - it('sets fields with provided form values', async () => { const values: FormValues = { email: 'foo@bar.com', diff --git a/client/src/www/views/contact_view/support_form/index.ts b/client/src/www/views/contact_view/support_form/index.ts index e80a5ff448..959e6e7970 100644 --- a/client/src/www/views/contact_view/support_form/index.ts +++ b/client/src/www/views/contact_view/support_form/index.ts @@ -14,21 +14,18 @@ * limitations under the License. */ -import {SelectedDetail} from '@material/mwc-menu/mwc-menu-base'; import {TextField} from '@material/mwc-textfield'; + import '@material/mwc-button'; import '@material/mwc-select'; import '@material/mwc-textarea'; import '@material/mwc-textfield'; - import {Localizer} from '@outline/infrastructure/i18n'; -import {html, css, LitElement, TemplateResult, nothing, PropertyValues} from 'lit'; +import {html, css, LitElement, PropertyValues} from 'lit'; import {customElement, property, state} from 'lit/decorators.js'; import {live} from 'lit/directives/live.js'; import {createRef, Ref, ref} from 'lit/directives/ref.js'; -import {AppType} from '../app_type'; - type FormControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; @@ -38,7 +35,6 @@ export declare interface FormValues { subject?: string; description?: string; accessKeySource?: string; - cloudProvider?: string; } /** Interface for valid form data. */ @@ -46,11 +42,7 @@ export declare interface ValidFormValues extends FormValues { email: string; subject: string; description: string; -} - -declare interface CloudProviderOption { - value: string; - label: string; + accessKeySource: string; } @customElement('support-form') @@ -89,12 +81,11 @@ export class SupportForm extends LitElement { private static readonly DEFAULT_MAX_LENGTH_INPUT = 225; /** The maximum character length of the "Description" field. */ private static readonly MAX_LENGTH_DESCRIPTION = 131072; - - private static readonly CLOUD_PROVIDERS = ['aws', 'digitalocean', 'gcloud']; + /** The number of visible text lines for the "Description" field. */ + private static readonly MAX_ROWS_DESCRIPTION = 5; @property({type: Function}) localize: Localizer = msg => msg; @property({type: Boolean}) disabled = false; - @property({type: String}) variant: AppType = AppType.CLIENT; @property({type: Object}) values: FormValues = {}; private readonly formRef: Ref = createRef(); @@ -138,60 +129,6 @@ export class SupportForm extends LitElement { this.checkFormValidity(); } - private get renderCloudProviderInputField(): TemplateResult | typeof nothing { - if (this.variant !== AppType.MANAGER) return nothing; - - const providers = SupportForm.CLOUD_PROVIDERS.map((provider): CloudProviderOption => { - return {value: provider, label: this.localize(`support-form-cloud-provider-${provider}`)}; - }); - /** We should sort the providers by their labels, which may be localized. */ - providers.sort(({label: labelA}, {label: labelB}) => { - if (labelA < labelB) { - return -1; - } else if (labelA === labelB) { - return 0; - } else { - return 1; - } - }); - providers.push({value: 'other', label: this.localize('support-form-cloud-provider-other')}); - - return html` - >) => { - if (e.detail.index !== -1) { - this.values.cloudProvider = providers[e.detail.index].value; - } - }} - @blur=${this.checkFormValidity} - > - ${providers.map(({value, label}) => html` ${label} `)} - - `; - } - - private get renderAccessKeySourceInputField(): TemplateResult | typeof nothing { - if (this.variant !== AppType.CLIENT) return nothing; - - return html` - - `; - } - render() { return html`
@@ -209,7 +146,16 @@ export class SupportForm extends LitElement { @blur=${this.checkFormValidity} > - ${this.renderCloudProviderInputField} ${this.renderAccessKeySourceInputField} + html`
-

outline-feedback-dialog

- - +
diff --git a/server_manager/web_app/ui_components/app-root.ts b/server_manager/web_app/ui_components/app-root.ts index 9e319c4442..f18ca03029 100644 --- a/server_manager/web_app/ui_components/app-root.ts +++ b/server_manager/web_app/ui_components/app-root.ts @@ -28,6 +28,7 @@ import '@polymer/paper-listbox/paper-listbox'; import '@polymer/paper-menu-button/paper-menu-button'; import './cloud-install-styles'; import './outline-about-dialog'; +import './outline-contact-us-dialog'; import './outline-do-oauth-step'; import './outline-gcp-oauth-step'; import '../outline-gcp-create-server-app'; @@ -412,7 +413,12 @@ export class AppRoot extends polymerElementWithLocalize { [[localize('nav-data-collection')]] - [[localize('nav-feedback')]] + + [[localize('nav-help')]] [[localize('nav-about')]]