Skip to content
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

feat(manager): add new contact form #1996

Merged
merged 15 commits into from
Apr 26, 2024
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
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
Loading