Skip to content

Commit

Permalink
Merge branch 'main' into jeffdaley/explore-projects
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffdaley committed Sep 26, 2023
2 parents 89f1f40 + 556659a commit 2ad8449
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Hermes

![](https://github.com/hashicorp-forge/hermes/workflows/ci/badge.svg)
[![CI](https://github.com/hashicorp-forge/hermes/workflows/ci/badge.svg?branch=main)](https://github.com/hashicorp-forge/hermes/actions/workflows/ci.yml?query=branch%3Amain)

> Hermes is not an official HashiCorp project.
> The repository contains software which is under active development and is in the alpha stage. Please read the “[Project Status](#project-status)” section for more information.
Expand Down
4 changes: 4 additions & 0 deletions configs/config.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// URL of the application.
base_url = "http://localhost:8000"

// log_format configures the logging format. Supported values are "standard" or
// "json".
log_format = "standard"

// algolia configures Hermes to work with Algolia.
algolia {
application_id = ""
Expand Down
14 changes: 14 additions & 0 deletions internal/cmd/commands/indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/hashicorp-forge/hermes/internal/indexer"
"github.com/hashicorp-forge/hermes/pkg/algolia"
gw "github.com/hashicorp-forge/hermes/pkg/googleworkspace"
"github.com/hashicorp/go-hclog"
)

type Command struct {
Expand Down Expand Up @@ -76,6 +77,19 @@ func (c *Command) Run(args []string) int {
return 1
}

// Configure logger.
switch cfg.LogFormat {
case "json":
log = hclog.New(&hclog.LoggerOptions{
JSONFormat: true,
})
case "standard":
case "":
default:
ui.Error(fmt.Sprintf("invalid value for log format: %s", cfg.LogFormat))
return 1
}

// Initialize database connection.
db, err := db.NewDB(*cfg.Postgres)
if err != nil {
Expand Down
14 changes: 14 additions & 0 deletions internal/cmd/commands/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/hashicorp-forge/hermes/pkg/links"
"github.com/hashicorp-forge/hermes/pkg/models"
"github.com/hashicorp-forge/hermes/web"
"github.com/hashicorp/go-hclog"
"gorm.io/gorm"
)

Expand Down Expand Up @@ -158,6 +159,19 @@ func (c *Command) Run(args []string) int {
}
}

// Configure logger.
switch cfg.LogFormat {
case "json":
c.Log = hclog.New(&hclog.LoggerOptions{
JSONFormat: true,
})
case "standard":
case "":
default:
c.UI.Error(fmt.Sprintf("invalid value for log format: %s", cfg.LogFormat))
return 1
}

// Build configuration for Okta authentication.
if !cfg.Okta.Disabled {
// Check for required Okta configuration.
Expand Down
4 changes: 4 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type Config struct {
// Indexer contains the configuration for the Hermes indexer.
Indexer *Indexer `hcl:"indexer,block"`

// LogFormat configures the logging format. Supported values are "standard" or
// "json".
LogFormat string `hcl:"log_format,optional"`

// Okta configures Hermes to work with Okta.
Okta *oktaalb.Config `hcl:"okta,block"`

Expand Down
2 changes: 2 additions & 0 deletions web/app/components/new/doc-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

{{! Title }}
<Hds::Form::TextInput::Field
data-test-title-input
@value={{this.title}}
@type="text"
name="title"
Expand All @@ -35,6 +36,7 @@
{{! Summary }}
<div class="relative">
<Hds::Form::Textarea::Field
data-test-summary-input
{{! TODO: add min-rows arg to auto-height-textarea}}
@value={{this.summary}}
name="summary"
Expand Down
17 changes: 5 additions & 12 deletions web/app/components/new/doc-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ interface DocFormErrors {
title: string | null;
summary: string | null;
productAbbreviation: string | null;
tags: string | null;
contributors: string | null;
}

Expand All @@ -33,7 +32,6 @@ const FORM_ERRORS: DocFormErrors = {
title: null,
summary: null,
productAbbreviation: null,
tags: null,
contributors: null,
};

Expand All @@ -53,7 +51,7 @@ export default class NewDocFormComponent extends Component<NewDocFormComponentSi

@tracked protected title: string = "";
@tracked protected summary: string = "";
@tracked protected productArea: string | undefined = undefined;
@tracked protected productArea?: string;
@tracked protected productAbbreviation: string | null = null;
@tracked protected contributors: HermesUser[] = [];

Expand Down Expand Up @@ -175,14 +173,7 @@ export default class NewDocFormComponent extends Component<NewDocFormComponentSi
* Validates the form and updates the `formErrors` property.
*/
private validate() {
const errors = { ...FORM_ERRORS };
if (this.productAbbreviation) {
if (/\d/.test(this.productAbbreviation)) {
errors.productAbbreviation =
"Product abbreviation can't include a number";
}
}
this.formErrors = errors;
this.formErrors = { ...FORM_ERRORS };
}

/**
Expand Down Expand Up @@ -246,7 +237,7 @@ export default class NewDocFormComponent extends Component<NewDocFormComponentSi

@action protected onProductSelect(
productName: string,
attributes?: ProductArea,
attributes: ProductArea,
) {
assert("attributes must exist", attributes);

Expand Down Expand Up @@ -305,6 +296,8 @@ export default class NewDocFormComponent extends Component<NewDocFormComponentSi
});
} catch (err: unknown) {
this.docIsBeingCreated = false;

// TODO: Improve error handling.
this.flashMessages.add({
title: "Error creating document draft",
message: `${err}`,
Expand Down
16 changes: 16 additions & 0 deletions web/mirage/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,22 @@ export default function (mirageConfig) {
return new Response(200, {}, matches.models);
});

/**
* Called when a user creates a new document.
*/
this.post("/drafts", (schema, request) => {
const document = schema.document.create({
...JSON.parse(request.requestBody),
});

document.update({
objectID: document.id,
owners: ["[email protected]"],
});

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

/**
* Used when publishing a draft for review.
* Updates the document's status and isDraft properties.
Expand Down
150 changes: 139 additions & 11 deletions web/tests/acceptance/authenticated/new/doc-test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import { click, visit } from "@ember/test-helpers";
import { click, fillIn, visit, waitFor } from "@ember/test-helpers";
import { setupApplicationTest } from "ember-qunit";
import { module, test } from "qunit";
import { authenticateSession } from "ember-simple-auth/test-support";
import { MirageTestContext, setupMirage } from "ember-cli-mirage/test-support";
import { getPageTitle } from "ember-page-title/test-support";
import { Response } from "miragejs";
import RouterService from "@ember/routing/router-service";
import window from "ember-window-mock";
import { DRAFT_CREATED_LOCAL_STORAGE_KEY } from "hermes/components/modals/draft-created";

// Selectors
const DOC_FORM = "[data-test-new-doc-form]";
const PRODUCT_SELECT = `${DOC_FORM} [data-test-product-select]`;
const PRODUCT_SELECT_TOGGLE = `${PRODUCT_SELECT} [data-test-x-dropdown-list-toggle-action]`;
const CREATE_BUTTON = `${DOC_FORM} [data-test-create-button]`;
const TITLE_INPUT = `${DOC_FORM} [data-test-title-input]`;
const SUMMARY_INPUT = `${DOC_FORM} [data-test-summary-input]`;
const PRODUCT_SELECT_ITEM = `${PRODUCT_SELECT} [data-test-x-dropdown-list-item]`;
const FIRST_PRODUCT_SELECT_ITEM_BUTTON = `${PRODUCT_SELECT_ITEM}:first-child button`;
const CREATING_NEW_DOC = "[data-test-creating-new-doc]";
const FLASH_NOTIFICATION = "[data-test-flash-notification]";
const DRAFT_CREATED_MODAL = "[data-test-draft-created-modal]";

interface AuthenticatedNewDocRouteTestContext extends MirageTestContext {}

Expand Down Expand Up @@ -36,36 +53,147 @@ module("Acceptance | authenticated/new/doc", function (hooks) {

await visit("/new/doc?docType=RFC");

const toggleSelector = "[data-test-x-dropdown-list-toggle-action]";
const thumbnailBadgeSelector = "[data-test-doc-thumbnail-product-badge]";

assert.dom(toggleSelector).exists();
assert.dom(`${toggleSelector} span`).hasText("Select a product/area");
assert.dom(PRODUCT_SELECT_TOGGLE).exists();
assert
.dom(`${toggleSelector} .flight-icon`)
.dom(`${PRODUCT_SELECT_TOGGLE} span`)
.hasText("Select a product/area");
assert
.dom(`${PRODUCT_SELECT_TOGGLE} .flight-icon`)
.hasAttribute("data-test-icon", "folder");

assert
.dom(thumbnailBadgeSelector)
.doesNotExist("badge not shown unless a product shortname exists");

await click(toggleSelector);
await click(PRODUCT_SELECT_TOGGLE);

const listItemSelector = "[data-test-x-dropdown-list-item]";
const lastItemSelector = `${listItemSelector}:last-child`;
const lastItemSelector = `${PRODUCT_SELECT_ITEM}:last-child`;

assert.dom(listItemSelector).exists({ count: 5 });
assert.dom(PRODUCT_SELECT_ITEM).exists({ count: 5 });
assert.dom(lastItemSelector).hasText("Terraform TF");
assert
.dom(lastItemSelector + " .flight-icon")
.hasAttribute("data-test-icon", "terraform");

await click(lastItemSelector + " button");

assert.dom(toggleSelector).hasText("Terraform TF");
assert.dom(PRODUCT_SELECT_TOGGLE).hasText("Terraform TF");
assert
.dom(toggleSelector + " .flight-icon")
.dom(PRODUCT_SELECT_TOGGLE + " .flight-icon")
.hasAttribute("data-test-icon", "terraform");
assert.dom(thumbnailBadgeSelector).exists();
});

test("the create button is disabled until the form requirements are met", async function (this: AuthenticatedNewDocRouteTestContext, assert) {
this.server.createList("product", 1);

await visit("/new/doc?docType=RFC");

assert.dom(CREATE_BUTTON).isDisabled();

await fillIn(TITLE_INPUT, "Foo");

assert.dom(CREATE_BUTTON).isDisabled();

await click(PRODUCT_SELECT_TOGGLE);
await click(FIRST_PRODUCT_SELECT_ITEM_BUTTON);

assert.dom(CREATE_BUTTON).isNotDisabled();

await fillIn(TITLE_INPUT, "");

assert.dom(CREATE_BUTTON).isDisabled();
});

test("it shows a loading screen while the doc is being created", async function (this: AuthenticatedNewDocRouteTestContext, assert) {
this.server.createList("product", 1);

await visit("/new/doc?docType=RFC");

await fillIn(TITLE_INPUT, "Foo");
await click(PRODUCT_SELECT_TOGGLE);
await click(FIRST_PRODUCT_SELECT_ITEM_BUTTON);

const clickPromise = click(CREATE_BUTTON);

await waitFor(CREATING_NEW_DOC);

assert.dom(CREATING_NEW_DOC).exists();

await clickPromise;
});

test("it shows an error screen if the doc creation fails", async function (this: AuthenticatedNewDocRouteTestContext, assert) {
this.server.createList("product", 1);

this.server.post("/drafts", () => {
return new Response(500);
});

await visit("/new/doc?docType=RFC");

await fillIn(TITLE_INPUT, "Foo");
await click(PRODUCT_SELECT_TOGGLE);
await click(FIRST_PRODUCT_SELECT_ITEM_BUTTON);

const clickPromise = click(CREATE_BUTTON);

await waitFor(FLASH_NOTIFICATION);

assert
.dom(FLASH_NOTIFICATION)
.containsText("Error creating document draft");

await clickPromise;
});

test("it redirects to the doc page if the doc is created successfully", async function (this: AuthenticatedNewDocRouteTestContext, assert) {
this.server.create("product", {
name: "Terraform",
});

// Turn off the modal
window.localStorage.setItem(DRAFT_CREATED_LOCAL_STORAGE_KEY, "true");

await visit("/new/doc?docType=RFC");

await fillIn(TITLE_INPUT, "Foo");

await fillIn(SUMMARY_INPUT, "Bar");

await click(PRODUCT_SELECT_TOGGLE);
await click(FIRST_PRODUCT_SELECT_ITEM_BUTTON);

await click(CREATE_BUTTON);

const routerService = this.owner.lookup("service:router") as RouterService;

assert.equal(routerService.currentRouteName, "authenticated.document");
assert.equal(routerService.currentURL, "/document/1?draft=true");

assert.dom("[data-test-document-title]").includesText("Foo");
assert.dom("[data-test-document-summary]").includesText("Bar");
assert.dom("[data-test-badge-dropdown-list]").includesText("Terraform");
});

test("it shows a confirmation modal when a draft is created", async function (this: AuthenticatedNewDocRouteTestContext, assert) {
// Reset the localStorage item
window.localStorage.removeItem(DRAFT_CREATED_LOCAL_STORAGE_KEY);

this.server.createList("product", 1);

await visit("/new/doc?docType=RFC");

await fillIn(TITLE_INPUT, "Foo");
await click(PRODUCT_SELECT_TOGGLE);
await click(FIRST_PRODUCT_SELECT_ITEM_BUTTON);

await click(CREATE_BUTTON);

await waitFor(DRAFT_CREATED_MODAL);

assert.dom(DRAFT_CREATED_MODAL).exists();
});
});

0 comments on commit 2ad8449

Please sign in to comment.