Skip to content

Commit

Permalink
Improve share modal UX
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffdaley committed Aug 16, 2023
1 parent de670c4 commit f8268e0
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 29 deletions.
1 change: 1 addition & 0 deletions web/app/components/document/modal.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<Hds::Modal
...attributes
@onClose={{@close}}
@color={{@color}}
@isDismissDisabled={{this.taskIsRunning}}
Expand Down
1 change: 1 addition & 0 deletions web/app/components/document/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { task } from "ember-concurrency";
import { HdsModalColor } from "hds/_shared";

interface DocumentModalComponentSignature {
Element: HTMLDialogElement;
Args: {
headerText: string;
bodyText?: string;
Expand Down
66 changes: 41 additions & 25 deletions web/app/components/document/sidebar.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@
{{#if this.isOwner}}
<div class="flex items-start gap-2 px-3">
<Hds::Button
data-test-sidebar-publish-for-review-button
@text="Publish for review..."
@size="medium"
@color="primary"
Expand Down Expand Up @@ -547,6 +548,7 @@

{{#if this.requestReviewModalIsShown}}
<Document::Modal
data-test-publish-for-review-modal
@headerText="Publish for review"
@errorTitle="Error requesting review"
@close={{this.closeRequestReviewModal}}
Expand All @@ -561,7 +563,10 @@
>
<:default as |M|>
{{#if M.taskIsRunning}}
<div class="grid place-items-center pt-1 pb-12">
<div
data-test-publishing-for-review-message
class="grid place-items-center pt-1 pb-12"
>
<div class="text-center">
<FlightIcon @name="loading" @size="24" class="mb-5" />
<h2>Submitting for review...</h2>
Expand Down Expand Up @@ -668,49 +673,60 @@
{{/if}}

{{#if this.docPublishedModalIsShown}}
<Hds::Modal as |M|>
<Hds::Modal data-test-doc-published-modal as |M|>
<M.Header>
<div class="flex items-center">
<FlightIcon
@name="check-circle-fill"
class="text-color-palette-green-200 mr-2"
class="mr-2 text-color-palette-green-200"
/>
Document published!
</div>
</M.Header>
<M.Body>
<p class="text-body-300 mb-5">
<p class="text-body-300">
Your doc is now available to everyone in your workspace.
<br />
Weʼve notified your approvers and subscribers to this product/area.
</p>
<label
class="hermes-form-label mb-1"
for="recently-published-share-url"
>
Share it with others
</label>
<Hds::Form::TextInput::Base
id="recently-published-share-url"
class="text-body-300 pl-2.5 mb-1.5"
@value={{this.shareURL}}
{{select-on-focus}}
readonly
/>
{{#unless this.docNumberLookupHasFailed}}
<label
class="hermes-form-label mb-1 mt-5"
for="recently-published-share-url"
>
Share it with others
</label>
<Hds::Form::TextInput::Base
data-test-share-document-url-input
id="recently-published-share-url"
class="mb-1.5 pl-2.5 text-body-300"
@value={{this.shareURL}}
{{select-on-focus}}
readonly
/>
{{/unless}}
</M.Body>
<M.Footer>
<Hds::ButtonSet>

<CopyURLButton
@url={{this.shareURL}}
class="hds-button hds-button--size-medium hds-button--color-primary"
/>
{{#unless this.docNumberLookupHasFailed}}
<CopyURLButton
data-test-doc-published-copy-url-button
@url={{this.shareURL}}
class="hds-button hds-button--size-medium hds-button--color-primary"
/>
{{/unless}}
<Hds::Button
data-test-continue-to-document-button
{{on "click" (set this "docPublishedModalIsShown" false)}}
@text="Continue to document"
@icon="arrow-right"
@iconPosition="trailing"
@color="tertiary"
@icon={{unless this.docNumberLookupHasFailed "arrow-right"}}
@iconPosition={{unless this.docNumberLookupHasFailed "trailing"}}
@color={{if this.docNumberLookupHasFailed "primary" "tertiary"}}
data-test-color={{if
this.docNumberLookupHasFailed
"primary"
"tertiary"
}}
/>
</Hds::ButtonSet>
</M.Footer>
Expand Down
48 changes: 45 additions & 3 deletions web/app/components/document/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ import Ember from "ember";
import htmlElement from "hermes/utils/html-element";
import ConfigService from "hermes/services/config";
import isValidURL from "hermes/utils/is-valid-u-r-l";
import { HermesDocumentType } from "hermes/types/document-type";

interface DocumentSidebarComponentSignature {
Args: {
profile: AuthenticatedUser;
document: HermesDocument;
docType: string;
docType: HermesDocumentType;
deleteDraft: (docId: string) => void;
isCollapsed: boolean;
toggleCollapsed: () => void;
Expand Down Expand Up @@ -76,8 +77,14 @@ export default class DocumentSidebarComponent extends Component<DocumentSidebarC
);
}

/**
* Whether the doc is a draft.
* If the draft was recently published, use that value
* to avoid a flicker of the "draft" state.
* Otherwise use the passed-in property.
*/
get isDraft() {
return this.args.document?.isDraft;
return this.draftWasPublished ? false : this.args.document?.isDraft;
}

get docID() {
Expand All @@ -93,6 +100,19 @@ export default class DocumentSidebarComponent extends Component<DocumentSidebarC
@tracked approvers = this.args.document.approvers || [];
@tracked product = this.args.document.product || "";

/**
* Whether a draft was published during the session.
* Set true when the user successfully requests a review.
* Used to immediately update the UI to reflect the doc's new state.
*/
@tracked private draftWasPublished: boolean | null = null;

/**
* Whether the `waitForDocNumber` task has has failed to find a docNumber.
* When true, the "doc published" modal will not show a URL or share button.
*/
@tracked protected docNumberLookupHasFailed = false;

/**
* Whether the draft's `isShareable` property is true.
* Checked on render and changed when the user toggles permissions.
Expand Down Expand Up @@ -155,7 +175,7 @@ export default class DocumentSidebarComponent extends Component<DocumentSidebarC
*/
protected get shareURL() {
// We only assign shortLinks to published documents
if (this.args.document.isDraft) {
if (this.isDraft) {
return window.location.href;
}

Expand Down Expand Up @@ -620,14 +640,36 @@ export default class DocumentSidebarComponent extends Component<DocumentSidebarC

this.refreshRoute();

await this.waitForDocNumber.perform();
this.draftWasPublished = true;
this.requestReviewModalIsShown = false;
this.docPublishedModalIsShown = true;
} catch (error: unknown) {
this.draftWasPublished = null;
this.maybeShowFlashError(error as Error, "Unable to request review");
throw error;
}
});

/**
* A task that awaits a newly published doc's docNumber assignment.
* In the unlikely case where the docNumber doesn't appear after 10 seconds,
* we remove the URL and share button from the "doc published" modal.
*/
private waitForDocNumber = task(async () => {
const numberOfTries = 10;

for (let i = 0; i < numberOfTries; i++) {
if (!this.args.document.docNumber.endsWith("?")) {
return;
} else {
await timeout(Ember.testing ? 0 : 1000);
}
}

this.docNumberLookupHasFailed = true;
});

deleteDraft = task(async () => {
try {
await this.args.deleteDraft(this.docID);
Expand Down
23 changes: 23 additions & 0 deletions web/mirage/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,29 @@ export default function (mirageConfig) {
return new Response(200, {}, matches.models);
});

/**
* Used when publishing a draft for review.
* Updates the document's status and isDraft properties.
*
* TODO: Add docNumber assignment.
*/
this.post("/reviews/:document_id", (schema, request) => {
const document = schema.document.findBy({
objectID: request.params.document_id,
});

if (document) {
document.update({
status: "In Review",
isDraft: false,
});

return new Response(200, {}, document.attrs);
}

return new Response(404, {}, {});
});

/**
* Used by the AuthenticatedUserService to add and remove subscriptions.
*/
Expand Down
84 changes: 83 additions & 1 deletion web/tests/acceptance/authenticated/document-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
triggerEvent,
visit,
waitFor,
waitUntil,
} from "@ember/test-helpers";
import { setupApplicationTest } from "ember-qunit";
import { module, test } from "qunit";
Expand All @@ -18,7 +19,6 @@ import {
} from "hermes/components/document/sidebar";
import { capitalize } from "@ember/string";
import window from "ember-window-mock";
import sinon from "sinon";
import { TEST_SHORT_LINK_BASE_URL } from "hermes/utils/hermes-urls";

const ADD_RELATED_RESOURCE_BUTTON_SELECTOR =
Expand Down Expand Up @@ -48,6 +48,21 @@ const READ_ONLY_PRODUCT_AREA_SELECTOR =
const READ_ONLY_CONTRIBUTORS_SELECTOR =
"[data-test-document-contributors-read-only]";
const READ_ONLY_APPROVERS_SELECTOR = "[data-test-document-approvers-read-only]";
const SIDEBAR_PUBLISH_FOR_REVIEW_BUTTON_SELECTOR =
"[data-test-sidebar-publish-for-review-button";
const PUBLISH_FOR_REVIEW_MODAL_SELECTOR =
"[data-test-publish-for-review-modal]";
const DOCUMENT_MODAL_PRIMARY_BUTTON_SELECTOR =
"[data-test-document-modal-primary-button]";
const PUBLISHING_FOR_REVIEW_MESSAGE_SELECTOR =
"[data-test-publishing-for-review-message]";
const DOC_PUBLISHED_MODAL_SELECTOR = "[data-test-doc-published-modal]";
const SHARE_DOCUMENT_URL_INPUT_SELECTOR =
"[data-test-share-document-url-input]";
const CONTINUE_TO_DOCUMENT_BUTTON_SELECTOR =
"[data-test-continue-to-document-button]";
const DOC_PUBLISHED_COPY_URL_BUTTON_SELECTOR =
"[data-test-doc-published-copy-url-button]";

const assertEditingIsDisabled = (assert: Assert) => {
assert.dom(EDITABLE_TITLE_SELECTOR).doesNotExist();
Expand Down Expand Up @@ -442,4 +457,71 @@ module("Acceptance | authenticated/document", function (hooks) {

assertEditingIsDisabled(assert);
});

test("doc owners can publish their docs for review", async function (this: AuthenticatedDocumentRouteTestContext, assert) {
this.server.create("document", {
objectID: 1,
isDraft: true,
docType: "PRD",
});

await visit("/document/1?draft=true");

await click(SIDEBAR_PUBLISH_FOR_REVIEW_BUTTON_SELECTOR);

assert.dom(PUBLISH_FOR_REVIEW_MODAL_SELECTOR).exists();

let clickPromise = click(DOCUMENT_MODAL_PRIMARY_BUTTON_SELECTOR);

await waitFor(PUBLISHING_FOR_REVIEW_MESSAGE_SELECTOR);
assert.dom(PUBLISHING_FOR_REVIEW_MESSAGE_SELECTOR).exists();

await clickPromise;

await waitFor(DOC_PUBLISHED_MODAL_SELECTOR);
assert.dom(DOC_PUBLISHED_MODAL_SELECTOR).exists();

assert
.dom(SHARE_DOCUMENT_URL_INPUT_SELECTOR)
.exists()
.hasValue(`${TEST_SHORT_LINK_BASE_URL}/prd/hcp-001`);

assert.dom(DOC_PUBLISHED_COPY_URL_BUTTON_SELECTOR).hasText("Copy link");
assert
.dom(CONTINUE_TO_DOCUMENT_BUTTON_SELECTOR)
.hasText("Continue to document")
.hasAttribute("data-test-color", "tertiary");

// TODO: Assert that clicking the modal dismisses it.
// Requires @hashicorp/design-system-components 2.9.0+
// https://github.com/hashicorp/design-system/commit/a6553ea032f70f0167f149589801b72154c3cf75
});

test('the "document published" modal hides the share elements if the docNumber fails to load', async function (this: AuthenticatedDocumentRouteTestContext, assert) {
this.server.create("document", {
objectID: 1,
isDraft: true,
docType: "PRD",
docNumber: "LAB-???",
});

await visit("/document/1?draft=true");

await click(SIDEBAR_PUBLISH_FOR_REVIEW_BUTTON_SELECTOR);
await click(DOCUMENT_MODAL_PRIMARY_BUTTON_SELECTOR);

await waitFor(DOC_PUBLISHED_MODAL_SELECTOR);
assert.dom(DOC_PUBLISHED_MODAL_SELECTOR).exists();

assert.dom(SHARE_DOCUMENT_URL_INPUT_SELECTOR).doesNotExist();
assert.dom(DOC_PUBLISHED_COPY_URL_BUTTON_SELECTOR).doesNotExist();

assert
.dom(CONTINUE_TO_DOCUMENT_BUTTON_SELECTOR)
.hasAttribute(
"data-test-color",
"primary",
"the Continue button becomes the primary button when the copy link is hidden"
);
});
});

0 comments on commit f8268e0

Please sign in to comment.