Skip to content

Commit

Permalink
feat(manager): add new contact form (#1996)
Browse files Browse the repository at this point in the history
* Remove manager support from client feedback form.

* Move issue_type exports to index.ts

* Add manager form.

* Some style changes.

* Send feedback to Sentry.

* Don't break the existing feedback flow.

* Fix gallery app.

* Fix submit button string.

* Fix lint issues.

* Update the installation failure flow.

* Address review comments.

* Fix missing cordova plugin in package-lock.

* Fix enum reference.
  • Loading branch information
sbruens authored Apr 26, 2024
1 parent cbec5b7 commit 7a413ca
Show file tree
Hide file tree
Showing 17 changed files with 3,014 additions and 247 deletions.
1 change: 0 additions & 1 deletion client/src/www/ui_components/app-root.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
21 changes: 0 additions & 21 deletions client/src/www/views/contact_view/app_type.ts

This file was deleted.

40 changes: 2 additions & 38 deletions client/src/www/views/contact_view/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OutlineErrorReporter>;

Expand All @@ -35,7 +35,7 @@ describe('ContactView client variant', () => {
Object.getOwnPropertyNames(SentryErrorReporter.prototype)
);
el = await fixture(
html` <contact-view .localize=${localize} variant="client" .errorReporter=${mockErrorReporter}></contact-view> `
html` <contact-view .localize=${localize} .errorReporter=${mockErrorReporter}></contact-view> `
);
});

Expand Down Expand Up @@ -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<OutlineErrorReporter> = jasmine.createSpyObj(
'SentryErrorReporter',
Object.getOwnPropertyNames(SentryErrorReporter.prototype)
);
el = await fixture(
html`
<contact-view .localize=${localize} variant="manager" .errorReporter=${mockErrorReporter}></contact-view>
`
);

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']);
});
});
});
66 changes: 35 additions & 31 deletions client/src/www/views/contact_view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -169,26 +174,26 @@ 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.
this.exitTemplate = this.localizeWithUrl(
`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() {
Expand All @@ -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 = {};
}

Expand Down Expand Up @@ -248,7 +253,6 @@ export class ContactView extends LitElement {
<support-form
${ref(this.formRef)}
.localize=${this.localize}
.variant=${this.variant}
.disabled=${this.isFormSubmitting}
.values=${this.formValues}
@cancel=${this.reset}
Expand All @@ -258,16 +262,16 @@ export class ContactView extends LitElement {
}

private get renderMainContent(): TemplateResult {
switch (this.step) {
case Step.FORM: {
switch (this.currentStep) {
case ProgressStep.FORM: {
return html` ${this.renderIntroTemplate} ${this.renderForm} `;
}

case Step.EXIT: {
case ProgressStep.EXIT: {
return html` <p class="exit">${this.exitTemplate}</p>`;
}

case Step.ISSUE_WIZARD:
case ProgressStep.ISSUE_WIZARD:
default: {
return html`
${this.renderIntroTemplate}
Expand Down Expand Up @@ -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`
<mwc-list-item value="${value}">
<span>${this.localize(`contact-view-issue-${value}`)}</span>
Expand Down
32 changes: 0 additions & 32 deletions client/src/www/views/contact_view/issue_type.ts

This file was deleted.

13 changes: 1 addition & 12 deletions client/src/www/views/contact_view/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<contact-view
.localize=${localize}
.variant=${variant}
.errorReporter=${{report: console.log}}
@success=${onSuccess}
@error=${onError}
Expand Down
14 changes: 0 additions & 14 deletions client/src/www/views/contact_view/support_form/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,6 @@ describe('SupportForm', () => {
expect(el).toBeInstanceOf(SupportForm);
});

it('shows correct fields for the client variant', async () => {
const el = await fixture(html` <support-form variant="client"></support-form> `);

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` <support-form variant="manager"></support-form> `);

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: '[email protected]',
Expand Down
Loading

0 comments on commit 7a413ca

Please sign in to comment.