Skip to content

Commit

Permalink
Merge pull request #245 from manifoldco/gui/8688-deprovision-button
Browse files Browse the repository at this point in the history
Adds a new deprovision button and it's resource-container wrapper
  • Loading branch information
Guillaume St-Pierre committed Jul 10, 2019
2 parents f3e5186 + c3de6cf commit d4ffdbb
Show file tree
Hide file tree
Showing 6 changed files with 437 additions and 0 deletions.
57 changes: 57 additions & 0 deletions docs/docs/data/manifold-data-deprovision-button.md
@@ -0,0 +1,57 @@
---
title: 🔒 Deprovision Button
path: /data/deprovision-button
example: |
<manifold-data-deprovision-button resource-label="my-resource">
Deprovison resource
</manifold-data-provision-button>
---

# 🔒 Deprovision Button

An unstyled button for deprovisioning resources. 🔒 Requires authentication.

## CTA text

Set the CTA text by adding anything between the opening and closing tags:

```html
<manifold-data-deprovision-button>
Deprovision My Resource
</manifold-data-provision-button>
```

`slot` can be attached to any HTML or JSX element. To read more about slots, see [Stencil’s Documentation][stencil-slot]

## Events

For validation, error, and success messages, it will emit custom events.

```js
document.addEventListener('manifold-deprovisionButton-click', ({ detail: { resourceName } }) =>
console.info(`⌛ Derovisioning ${resourceName}`)
);
document.addEventListener(
'manifold-deprovisionButton-success',
({ details: { resourceName } }) =>alert(`${resourceName} deprovisioned successfully!`)
);
document.addEventListener('manifold-deprovisionButton-error', ({ detail }) => console.log(detail));
// {message: "bad_request: bad_request: No plan_id provided", resourceid: "1234", resourceName: "my-resource"}
```

| Name | Returns | Description |
| :--------------------------------- | :--------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------- |
| `manifold-deprovisionButton-click` | `resourceName` | Fires immediately when button is clicked. May be used to trigger a loading state, until `-success` or `-error` is received. |
| `manifold-deprovisionButton-success` | `message`, `resourceId`, `resourceName` | Successful deprovision. Returns a resource ID and resource Label. |
| `manifold-deprovisionButton-error` | `message`, `resourceId`, `resourceName` | Erred provision, along with information on what went wrong. |

## Styling

Whereas other components in this system take advantage of [Shadow
DOM][shadow-dom] encapsulation for ease of use, we figured this component
should be customizable. As such, style it however you’d like! We recommend
attaching styles to a parent element using any CSS-in-JS framework of your
choice, or plain ol’ CSS.

[shadow-dom]: https://developers.google.com/web/fundamentals/web-components/shadowdom
[stencil-slot]: https://stenciljs.com/docs/templating-jsx/
53 changes: 53 additions & 0 deletions src/components.d.ts
Expand Up @@ -69,6 +69,22 @@ export namespace Components {
'isCustomizable'?: boolean;
'measuredFeatures': Catalog.ExpandedFeature[];
}
interface ManifoldDataDeprovisionButton {
/**
* _(hidden)_ Passed by `<manifold-connection>`
*/
'authToken'?: string;
/**
* _(hidden)_ Passed by `<manifold-connection>`
*/
'connection': Connection;
'loading'?: boolean;
'resourceId'?: string;
/**
* The label of the resource to deprovision
*/
'resourceName'?: string;
}
interface ManifoldDataHasResource {
/**
* _(hidden)_ Passed by `<manifold-connection>`
Expand Down Expand Up @@ -438,6 +454,7 @@ export namespace Components {
'credentials'?: Marketplace.Credential[];
'resourceState': ResourceState;
}
interface ManifoldResourceDeprovision {}
interface ManifoldResourceDetails {}
interface ManifoldResourceDetailsView {
'data'?: Gateway.Resource;
Expand Down Expand Up @@ -584,6 +601,12 @@ declare global {
new (): HTMLManifoldCostDisplayElement;
};

interface HTMLManifoldDataDeprovisionButtonElement extends Components.ManifoldDataDeprovisionButton, HTMLStencilElement {}
var HTMLManifoldDataDeprovisionButtonElement: {
prototype: HTMLManifoldDataDeprovisionButtonElement;
new (): HTMLManifoldDataDeprovisionButtonElement;
};

interface HTMLManifoldDataHasResourceElement extends Components.ManifoldDataHasResource, HTMLStencilElement {}
var HTMLManifoldDataHasResourceElement: {
prototype: HTMLManifoldDataHasResourceElement;
Expand Down Expand Up @@ -752,6 +775,12 @@ declare global {
new (): HTMLManifoldResourceCredentialsViewElement;
};

interface HTMLManifoldResourceDeprovisionElement extends Components.ManifoldResourceDeprovision, HTMLStencilElement {}
var HTMLManifoldResourceDeprovisionElement: {
prototype: HTMLManifoldResourceDeprovisionElement;
new (): HTMLManifoldResourceDeprovisionElement;
};

interface HTMLManifoldResourceDetailsElement extends Components.ManifoldResourceDetails, HTMLStencilElement {}
var HTMLManifoldResourceDetailsElement: {
prototype: HTMLManifoldResourceDetailsElement;
Expand Down Expand Up @@ -855,6 +884,7 @@ declare global {
'manifold-button-link': HTMLManifoldButtonLinkElement;
'manifold-connection': HTMLManifoldConnectionElement;
'manifold-cost-display': HTMLManifoldCostDisplayElement;
'manifold-data-deprovision-button': HTMLManifoldDataDeprovisionButtonElement;
'manifold-data-has-resource': HTMLManifoldDataHasResourceElement;
'manifold-data-manage-button': HTMLManifoldDataManageButtonElement;
'manifold-data-product-logo': HTMLManifoldDataProductLogoElement;
Expand Down Expand Up @@ -883,6 +913,7 @@ declare global {
'manifold-resource-container': HTMLManifoldResourceContainerElement;
'manifold-resource-credentials': HTMLManifoldResourceCredentialsElement;
'manifold-resource-credentials-view': HTMLManifoldResourceCredentialsViewElement;
'manifold-resource-deprovision': HTMLManifoldResourceDeprovisionElement;
'manifold-resource-details': HTMLManifoldResourceDetailsElement;
'manifold-resource-details-view': HTMLManifoldResourceDetailsViewElement;
'manifold-resource-list': HTMLManifoldResourceListElement;
Expand Down Expand Up @@ -948,6 +979,25 @@ declare namespace LocalJSX {
'isCustomizable'?: boolean;
'measuredFeatures'?: Catalog.ExpandedFeature[];
}
interface ManifoldDataDeprovisionButton extends JSXBase.HTMLAttributes<HTMLManifoldDataDeprovisionButtonElement> {
/**
* _(hidden)_ Passed by `<manifold-connection>`
*/
'authToken'?: string;
/**
* _(hidden)_ Passed by `<manifold-connection>`
*/
'connection'?: Connection;
'loading'?: boolean;
'onManifold-deprovisionButton-click'?: (event: CustomEvent<any>) => void;
'onManifold-deprovisionButton-error'?: (event: CustomEvent<any>) => void;
'onManifold-provisionButton-success'?: (event: CustomEvent<any>) => void;
'resourceId'?: string;
/**
* The label of the resource to deprovision
*/
'resourceName'?: string;
}
interface ManifoldDataHasResource extends JSXBase.HTMLAttributes<HTMLManifoldDataHasResourceElement> {
/**
* _(hidden)_ Passed by `<manifold-connection>`
Expand Down Expand Up @@ -1331,6 +1381,7 @@ declare namespace LocalJSX {
'onCredentialsRequested'?: (event: CustomEvent<any>) => void;
'resourceState'?: ResourceState;
}
interface ManifoldResourceDeprovision extends JSXBase.HTMLAttributes<HTMLManifoldResourceDeprovisionElement> {}
interface ManifoldResourceDetails extends JSXBase.HTMLAttributes<HTMLManifoldResourceDetailsElement> {}
interface ManifoldResourceDetailsView extends JSXBase.HTMLAttributes<HTMLManifoldResourceDetailsViewElement> {
'data'?: Gateway.Resource;
Expand Down Expand Up @@ -1443,6 +1494,7 @@ declare namespace LocalJSX {
'manifold-button-link': ManifoldButtonLink;
'manifold-connection': ManifoldConnection;
'manifold-cost-display': ManifoldCostDisplay;
'manifold-data-deprovision-button': ManifoldDataDeprovisionButton;
'manifold-data-has-resource': ManifoldDataHasResource;
'manifold-data-manage-button': ManifoldDataManageButton;
'manifold-data-product-logo': ManifoldDataProductLogo;
Expand Down Expand Up @@ -1471,6 +1523,7 @@ declare namespace LocalJSX {
'manifold-resource-container': ManifoldResourceContainer;
'manifold-resource-credentials': ManifoldResourceCredentials;
'manifold-resource-credentials-view': ManifoldResourceCredentialsView;
'manifold-resource-deprovision': ManifoldResourceDeprovision;
'manifold-resource-details': ManifoldResourceDetails;
'manifold-resource-details-view': ManifoldResourceDetailsView;
'manifold-resource-list': ManifoldResourceList;
Expand Down
@@ -0,0 +1,170 @@
import { newSpecPage } from '@stencil/core/testing';
import fetchMock from 'fetch-mock';

import { Resource } from '../../spec/mock/marketplace';
import { connections } from '../../utils/connections';

import { ManifoldDataDeprovisionButton } from './manifold-data-deprovision-button';

describe('<manifold-data-provision-button>', () => {
it('fetches the resource id on load if not set', () => {
const resourceName = 'test-resource';

const provisionButton = new ManifoldDataDeprovisionButton();
provisionButton.fetchResourceId = jest.fn();
provisionButton.resourceName = resourceName;
provisionButton.componentWillLoad();
expect(provisionButton.fetchResourceId).toHaveBeenCalledWith(resourceName);
});

it('does not fetch the resource id on load if set', () => {
const resourceName = 'test-resource';

const provisionButton = new ManifoldDataDeprovisionButton();
provisionButton.fetchResourceId = jest.fn();
provisionButton.resourceName = resourceName;
provisionButton.resourceId = resourceName;
provisionButton.componentWillLoad();
expect(provisionButton.fetchResourceId).not.toHaveBeenCalled();
});

it('fetches resource id on change if not set', () => {
const resourceName = 'new-resource';

const provisionButton = new ManifoldDataDeprovisionButton();
provisionButton.fetchResourceId = jest.fn();
provisionButton.resourceName = 'old-resource';

provisionButton.nameChange(resourceName);
expect(provisionButton.fetchResourceId).toHaveBeenCalledWith(resourceName);
});

it('does not resource id on change if set', () => {
const resourceName = 'new-resource';

const provisionButton = new ManifoldDataDeprovisionButton();
provisionButton.fetchResourceId = jest.fn();
provisionButton.resourceName = 'old-resource';
provisionButton.resourceId = '1234';

provisionButton.nameChange(resourceName);
expect(provisionButton.fetchResourceId).not.toHaveBeenCalled();
});

describe('when created without a resource ID', () => {
afterEach(() => {
fetchMock.restore();
});

it('will fetch the resource id', async () => {
const resourceName = 'new-resource';

fetchMock.mock(`${connections.prod.marketplace}/resources/?me&label=${resourceName}`, [
Resource,
]);

const page = await newSpecPage({
components: [ManifoldDataDeprovisionButton],
html: `
<manifold-data-deprovision-button
resource-name="${resourceName}"
>Deprovision</manifold-data-deprovision-button>
`,
});

expect(
fetchMock.called(`${connections.prod.marketplace}/resources/?me&label=${resourceName}`)
).toBe(true);

const root = page.rootInstance as ManifoldDataDeprovisionButton;
expect(root.resourceId).toEqual(Resource.id);
});

it('will do nothing on a fetch error', async () => {
const resourceName = 'new-resource';

fetchMock.mock(`${connections.prod.marketplace}/resources/?me&name=${resourceName}`, []);

const page = await newSpecPage({
components: [ManifoldDataDeprovisionButton],
html: `
<manifold-data-deprovision-button
resource-name="${resourceName}"
>Deprovision</manifold-data-deprovision-button>
`,
});

expect(
fetchMock.called(`${connections.prod.marketplace}/resources/?me&label=${resourceName}`)
).toBe(true);

const root = page.rootInstance as ManifoldDataDeprovisionButton;
expect(root.resourceId).not.toEqual(Resource.id);
});
});

describe('When sending a request to deprovision', () => {
afterEach(() => {
fetchMock.restore();
});

beforeEach(() => {
fetchMock.mock(/.*\/resources\/.*/, [Resource]);
});

it('will trigger a dom event on successful deprovision', async () => {
fetchMock.mock(`${connections.prod.gateway}/id/resource/${Resource.id}`, 200);

const page = await newSpecPage({
components: [ManifoldDataDeprovisionButton],
html: `
<manifold-data-deprovision-button
resource-name="test"
>Deprovision</manifold-data-deprovision-button>
`,
});

const instance = page.rootInstance as ManifoldDataDeprovisionButton;
instance.successEvent.emit = jest.fn();

await instance.deprovision();

expect(fetchMock.called(`${connections.prod.gateway}/id/resource/${Resource.id}`)).toBe(true);
expect(instance.successEvent.emit).toHaveBeenCalledWith({
message: 'test successfully deprovisioned',
resourceName: 'test',
resourceId: Resource.id,
});
});

it('will trigger a dom event on failed deprovision', async () => {
fetchMock.mock(`${connections.prod.gateway}/id/resource/${Resource.id}`, {
status: 500,
body: {
message: 'ohnoes',
},
});

const page = await newSpecPage({
components: [ManifoldDataDeprovisionButton],
html: `
<manifold-data-deprovision-button
resource-name="test"
>Provision</manifold-data-deprovision-button>
`,
});

const instance = page.rootInstance as ManifoldDataDeprovisionButton;
instance.errorEvent.emit = jest.fn();

await instance.deprovision();

expect(fetchMock.called(`${connections.prod.gateway}/id/resource/${Resource.id}`)).toBe(true);
expect(instance.errorEvent.emit).toHaveBeenCalledWith({
message: 'ohnoes',
resourceName: 'test',
resourceId: Resource.id,
});
});
});
});

0 comments on commit d4ffdbb

Please sign in to comment.