Skip to content

Commit

Permalink
feat(www): add initial draft of a new contact view (#1710)
Browse files Browse the repository at this point in the history
* Add new contact us view.

* Rough first draft of the new contact flow.

* Add new contact us view to the app based on conditional `enableNewContactView` property.

* Remove use of localize for the time being. We'll localize in a distinct step later.

* Add validity checks to form.

* Update support links.

* Update form field names.

* Make some methods private and add some docstrings.

* Add initial test of support form.

* Add initial test of contact view.

* Revert legacy feedback view ID rename.

* Undo linting change in unrelated files.

* Add Jasmine types.

* Exclude specs from the build.

* Address some nullability checks in the contact view test file.

* Fix Electron builds.

* Fix some TypeScript compiler errors.

* Add more `ContactView` test cases.

* Parameterize test.

* Add more `SupportForm` test cases.

* Add test cases for the client vs. manager variants.

* Add controls for the `ContactView` storybook example to switch between client and manager variants.

* Rename `hasOpenTicketSelection` to avoid the implication that it's a boolean field.

* Use type-safe equality operators.

* Prefer setting properties over attributes.

* Use `render...()` prefix in methods returning `TemplateResult` objects.

* Update styling with some comments.

* Rename flag to make it clear it's ephemeral.

* Add a "cancel" button that cancels the form instead of using the back button to clear the entire element.

* Hide the issue selector on reset.

* Add a `FormValues` object and link them to the form fields so we set values more declaratively.

* Add test case for resetting the form.

* Set correct `min-height`.

* Add missing license headers.

* Simplify cancel button test case setup.

* Use guard clause instead of ternary operator for progress bar render method.

* Hoist maximum input lengths to static variables.

* Move submission and cancellation logic from the `SupportForm` up to the `ContactView` component.

* Remove unused `issueType` property on `SupportForm`.

* Pass form values into the `SupportForm` component.

* Update tests.

* Fix field names.

* Add test case for setting `values` property.

* Remove hidden OS and version fields.

* Bring issue type values in line with existing feedback values.

* Fix existing feedback page.
  • Loading branch information
sbruens authored Sep 29, 2023
1 parent efc1dbf commit d8c3ef8
Show file tree
Hide file tree
Showing 21 changed files with 9,552 additions and 192 deletions.
8,555 changes: 8,385 additions & 170 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@
},
"dependencies": {
"@material/mwc-button": "^0.25.3",
"@material/mwc-circular-progress": "^0.27.0",
"@material/mwc-formfield": "^0.25.3",
"@material/mwc-icon-button": "^0.25.3",
"@material/mwc-menu": "^0.25.3",
"@material/mwc-radio": "^0.25.3",
"@material/mwc-select": "^0.25.3",
"@material/mwc-textarea": "^0.25.3",
"@material/mwc-textfield": "^0.25.3",
"@polymer/app-layout": "^3.1.0",
"@polymer/app-localize-behavior": "^3.0.1",
"@polymer/app-route": "^3.0.2",
Expand Down Expand Up @@ -67,10 +73,12 @@
"@babel/preset-env": "^7.12.11",
"@commitlint/config-conventional": "^16.2.4",
"@jsdevtools/coverage-istanbul-loader": "^3.0.5",
"@open-wc/testing": "^3.2.0",
"@open-wc/testing-karma": "^4.0.9",
"@rollup/plugin-image": "^2.1.1",
"@types/auto-launch": "^5.0.0",
"@types/cordova": "^0.0.34",
"@types/jasmine": "^2.8.6",
"@types/jasmine": "^4.3.6",
"@types/node": "^14.14.7",
"@types/polymer": "^1.2.9",
"@types/uuidv4": "^2.0.0",
Expand All @@ -92,6 +100,7 @@
"cordova-plugin-outline": "file:src/cordova/plugin",
"cordova-webintent": "github:cordova-misc/cordova-webintent#v2.0.0",
"css-loader": "^5.0.1",
"deepmerge": "^4.3.1",
"electron": "^19.1.9",
"electron-builder": "^23.6.0",
"electron-icon-maker": "^0.0.5",
Expand All @@ -106,7 +115,7 @@
"i18n-strings-files": "^2.0.0",
"intl-messageformat": "^9.12.0",
"istanbul": "^0.4.5",
"karma": "^6.3.16",
"karma": "^6.4.2",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage-istanbul-reporter": "^3.0.3",
"karma-jasmine": "^4.0.1",
Expand All @@ -117,7 +126,7 @@
"outline-i18n": "Jigsaw-Code/outline-i18n#v0.0.7",
"postcss": "^7.0.39",
"postcss-rtl": "^1.7.3",
"prettier": "^1.19.1",
"prettier": "^2.8.0",
"pretty-quick": "^2.0.1",
"puppeteer": "^13.1.2",
"replace-in-file": "^6.3.5",
Expand Down
5 changes: 2 additions & 3 deletions src/electron/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"extends": "../../tsconfig.json",
"extends": "../www/tsconfig.json",
"compilerOptions": {
"outDir": "../../build/electron",
"lib": ["es2022", "dom"]
"outDir": "../../build/electron"
}
}
4 changes: 3 additions & 1 deletion src/www/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ export class App {
this.rootEl.addEventListener('QuitPressed', this.quitApplication.bind(this));
this.rootEl.addEventListener('AutoConnectDialogDismissed', this.autoConnectDialogDismissed.bind(this));
this.rootEl.addEventListener('ShowServerRename', this.rootEl.showServerRename.bind(this.rootEl));
this.feedbackViewEl.$.submitButton.addEventListener('tap', this.submitFeedback.bind(this));
if (this.feedbackViewEl) {
this.feedbackViewEl.$.submitButton.addEventListener('tap', this.submitFeedback.bind(this));
}
this.rootEl.addEventListener('PrivacyTermsAcked', this.ackPrivacyTerms.bind(this));
this.rootEl.addEventListener('SetLanguageRequested', this.setAppLanguage.bind(this));

Expand Down
Binary file added src/www/assets/icons/contact.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/www/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"connect-button-label": "Connect",
"connected-server-state": "Connected",
"connecting-server-state": "Connecting...",
"contact-page-title": "Contact us",
"data-collection": "Data collection",
"disconnect-button-label": "Disconnect",
"disconnected-server-state": "Disconnected",
Expand Down
2 changes: 1 addition & 1 deletion src/www/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../www",
"lib": ["es2022", "dom"]
"lib": ["es2022", "dom", "dom.iterable"]
}
}
31 changes: 26 additions & 5 deletions src/www/ui_components/app-root.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import './language-view.js';
import './licenses-view.js';
import './outline-icons.js';
import './privacy-view.js';
import '../views/contact_view';
import '../views/servers_view';
import './server-rename-dialog.js';
import './user-comms-dialog.js';
Expand Down Expand Up @@ -91,6 +92,7 @@ export class AppRoot extends mixinBehaviors([AppLocalizeBehavior], PolymerElemen
app-toolbar [main-title] {
flex: 2 1 100%;
text-transform: capitalize;
}
app-toolbar img {
Expand Down Expand Up @@ -177,6 +179,7 @@ export class AppRoot extends mixinBehaviors([AppLocalizeBehavior], PolymerElemen
#drawer-nav paper-item {
min-height: 32px;
text-transform: capitalize;
}
.first-menu-item {
Expand Down Expand Up @@ -291,7 +294,12 @@ export class AppRoot extends mixinBehaviors([AppLocalizeBehavior], PolymerElemen
<iron-pages id="pages" selected="[[page]]" attr-for-selected="name">
<servers-view name="servers" id="serversView" servers="[[servers]]" localize="[[localize]]" use-alt-access-message="[[useAltAccessMessage]]""></servers-view>
<feedback-view name="feedback" id="feedbackView" localize="[[localize]]"></feedback-view>
<template is="dom-if" if="{{contactViewFeatureFlag}}">
<contact-view name="contact" id="contactView"></contact-view>
</template>
<template is="dom-if" if="{{!contactViewFeatureFlag}}">
<feedback-view name="feedback" id="feedbackView" localize="[[localize]]"></feedback-view>
</template>
<about-view
name="about"
id="aboutView"
Expand Down Expand Up @@ -363,10 +371,18 @@ export class AppRoot extends mixinBehaviors([AppLocalizeBehavior], PolymerElemen
<img src$="[[rootPath]]assets/icons/outline.png" alt="outline" />
<span class="item-label">[[localize('servers-menu-item')]]</span>
</paper-item>
<paper-item name="feedback">
<img src$="[[rootPath]]assets/icons/feedback.png" alt="feedback" />
[[localize('feedback-page-title')]]
</paper-item>
<template is="dom-if" if="{{contactViewFeatureFlag}}">
<paper-item name="contact">
<img src$="[[rootPath]]assets/icons/contact.png" alt="contact" />
[[localize('contact-page-title')]]
</paper-item>
</template>
<template is="dom-if" if="{{!contactViewFeatureFlag}}">
<paper-item name="feedback">
<img src$="[[rootPath]]assets/icons/feedback.png" alt="feedback" />
[[localize('feedback-page-title')]]
</paper-item>
</template>
<paper-item name="about">
<img src$="[[rootPath]]assets/icons/about.png" alt="about" />
[[localize('about-page-title')]]
Expand Down Expand Up @@ -579,6 +595,11 @@ export class AppRoot extends mixinBehaviors([AppLocalizeBehavior], PolymerElemen
type: Boolean,
computed: '_computeUseAltAccessMessage(language)',
},
contactViewFeatureFlag: {
type: Boolean,
readonly: true,
value: false,
},
};
}

Expand Down
17 changes: 9 additions & 8 deletions src/www/ui_components/feedback-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,12 @@ Polymer({
},
},

ready: function() {
var appRoot = dom(this).getOwnerRoot().host;
ready: function () {
let appRoot = dom(this).getOwnerRoot().host;
window.addEventListener(
'location-changed',
function() {
function () {
appRoot = appRoot ?? dom(this).getOwnerRoot().host;
if (appRoot.page !== 'feedback') return;
// Workaround:
// https://github.com/PolymerElements/paper-dropdown-menu/issues/159#issuecomment-229958448
Expand All @@ -154,11 +155,11 @@ Polymer({
);
},

_emailValueChanged: function() {
_emailValueChanged: function () {
this.hasEnteredEmail = !!this.$.email.value;
},

_computeSubmitButtonLabel: function(submitting, localize) {
_computeSubmitButtonLabel: function (submitting, localize) {
// If localize hasn't been defined yet, just return '' for now - Polymer will call this
// again once localize has been defined at which point we will return the right value.
if (!localize) return '';
Expand All @@ -168,11 +169,11 @@ Polymer({

// Returns whether the window's locale is English (i.e. EN, en-US) and the user has
// entered their email address.
_computeShouldShowLanguageDisclaimer: function(hasEnteredEmail) {
_computeShouldShowLanguageDisclaimer: function (hasEnteredEmail) {
return !window.navigator.language.match(/^en/i) && hasEnteredEmail;
},

getValidatedFormData: function() {
getValidatedFormData: function () {
var inputs = [this.$.categoryList, this.$.feedback, this.$.email];
for (var i = 0, input = inputs[i]; input; input = inputs[++i]) {
if (input.validate && !input.validate()) {
Expand All @@ -189,7 +190,7 @@ Polymer({
};
},

resetForm: function() {
resetForm: function () {
this.$.categoryList.category = 'general';
this.$.feedback.value = '';
this.$.email.value = '';
Expand Down
21 changes: 21 additions & 0 deletions src/www/views/contact_view/app_type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* 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',
}
133 changes: 133 additions & 0 deletions src/www/views/contact_view/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* 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.
*/

import {ContactView} from './index';

import {fixture, html, nextFrame} from '@open-wc/testing';
import {SupportForm} from './support_form';

describe('ContactView', () => {
let el: ContactView;

beforeEach(async () => {
el = await fixture(html` <contact-view></contact-view> `);
});

it('is defined', async () => {
expect(el).toBeInstanceOf(ContactView);
});

it('hides issue selector by default', async () => {
const issueSelector = el.shadowRoot?.querySelector('mwc-select[label="Outline issue"]');
expect(issueSelector?.hasAttribute('hidden')).toBeTrue();
});

it('hides support form by default', async () => {
const supportForm = el.shadowRoot?.querySelector('support-form');
expect(supportForm).toBeNull();
});

it('shows exit message if the user selects that they have an open ticket', async () => {
const radioButton = el.shadowRoot!.querySelector('mwc-formfield[label="Yes"] mwc-radio')! as HTMLElement;
radioButton.click();
await nextFrame();

const exitCard = el.shadowRoot!.querySelector('outline-card')!;
expect(exitCard.textContent).toContain('experiencing high support volume');
});

it('shows issue selector if the user selects that they have no open tickets', async () => {
const radioButton = el.shadowRoot!.querySelector('mwc-formfield[label="No"] mwc-radio')! as HTMLElement;
radioButton.click();
await nextFrame();

const issueSelector = el.shadowRoot!.querySelector('mwc-select[label="Outline issue"]');
expect(issueSelector?.hasAttribute('hidden')).toBeFalse();
});

describe('when the user selects issue', () => {
let issueSelector: HTMLElement;

beforeEach(async () => {
issueSelector = el.shadowRoot!.querySelector('mwc-select[label="Outline issue"]')!;
const radioButton: HTMLElement = el.shadowRoot!.querySelector('mwc-formfield[label="No"] mwc-radio')!;
radioButton.click();
await nextFrame();
});

const conditions = [
{
testcaseName: 'I need an access key',
value: 'no-server',
expectedMsg: 'does not distribute free or paid access keys',
},
{
testcaseName: 'I am having trouble adding a server using my access key',
value: 'cannot-add-server',
expectedMsg: 'assist with adding a server',
},
{
testcaseName: 'I am having trouble connecting to my Outline VPN server',
value: 'connection',
expectedMsg: 'assist with connecting to a server',
},
];

for (const {testcaseName, value, expectedMsg} of conditions) {
it(`'${testcaseName}' shows exit message`, async () => {
const issue: HTMLElement = issueSelector.querySelector(`mwc-list-item[value="${value}"]`)!;
issue.click();
await nextFrame();

const exitCard = el.shadowRoot!.querySelector('outline-card')!;
expect(exitCard.textContent).toContain(expectedMsg);
});
}

describe('"General feedback & suggestions"', () => {
beforeEach(async () => {
const issue: HTMLElement = issueSelector.querySelector('mwc-list-item[value="general"]')!;
issue.click();
await nextFrame();
});

it('shows support form', async () => {
const supportForm = el.shadowRoot!.querySelector('support-form')!;
expect(supportForm).not.toBeNull();
});

it('shows "thank you" exit message on completion of support form', async () => {
const supportForm: SupportForm = el.shadowRoot!.querySelector('support-form')!;
supportForm.valid = true;
supportForm.dispatchEvent(new CustomEvent('submit'));

await nextFrame();

const exitCard = el.shadowRoot!.querySelector('outline-card')!;
expect(exitCard.textContent).toContain('Thanks for helping us improve');
});

it('shows default contact view on cancellation of support form', async () => {
el.shadowRoot!.querySelector('support-form')!.dispatchEvent(new CustomEvent('cancel'));

await nextFrame();

expect(el.shadowRoot?.querySelector('p')?.textContent).toContain('Tell us how we can help.');
expect(el.shadowRoot?.querySelector('support-form')).toBeNull();
});
});
});
});
Loading

0 comments on commit d8c3ef8

Please sign in to comment.