From 50d614098ae0868c77445a494da1c603f8ddf701 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Fri, 4 Mar 2022 04:36:33 -0500 Subject: [PATCH] feat: add a new `` component (#46073) (#46230) (cherry picked from commit 47d1d83673e491b3eb14da8c73db94a845bd9159) Co-authored-by: Levente Balogh --- .../SecretInput/SecretInput.story.tsx | 60 +++++++++++++++++ .../SecretInput/SecretInput.test.tsx | 65 +++++++++++++++++++ .../components/SecretInput/SecretInput.tsx | 26 ++++++++ .../src/components/SecretInput/index.tsx | 1 + 4 files changed, 152 insertions(+) create mode 100644 packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx create mode 100644 packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx create mode 100644 packages/grafana-ui/src/components/SecretInput/SecretInput.tsx create mode 100644 packages/grafana-ui/src/components/SecretInput/index.tsx diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx new file mode 100644 index 00000000000..60da75ef7de --- /dev/null +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.story.tsx @@ -0,0 +1,60 @@ +import React, { useState, ChangeEvent } from 'react'; +import { Story, Meta } from '@storybook/react'; +import { SecretInput, Props } from './SecretInput'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +export default { + title: 'Forms/SecretInput', + component: SecretInput, + decorators: [withCenteredStory], + parameters: { + controls: { + exclude: [ + 'prefix', + 'suffix', + 'addonBefore', + 'addonAfter', + 'type', + 'disabled', + 'invalid', + 'loading', + 'before', + 'after', + ], + }, + }, + args: { + width: 50, + placeholder: 'Enter your secret...', + }, + argTypes: { + width: { control: { type: 'range', min: 10, max: 200, step: 10 } }, + }, +} as Meta; + +const Template: Story = (args) => { + const [secret, setSecret] = useState(''); + + return ( + ) => setSecret(event.target.value.trim())} + onReset={() => setSecret('')} + /> + ); +}; + +export const basic = Template.bind({}); + +basic.args = { + isConfigured: false, +}; + +export const secretIsConfigured = Template.bind({}); + +secretIsConfigured.args = { + isConfigured: true, +}; diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx new file mode 100644 index 00000000000..010ed2fdf59 --- /dev/null +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.test.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SecretInput, RESET_BUTTON_TEXT, CONFIGURED_TEXT } from './SecretInput'; + +const PLACEHOLDER_TEXT = 'Your secret...'; + +describe('', () => { + it('should render an input if the secret is not configured', () => { + render( {}} onReset={() => {}} placeholder={PLACEHOLDER_TEXT} />); + + const input = screen.getByPlaceholderText(PLACEHOLDER_TEXT); + + // Should show an enabled input + expect(input).toBeInTheDocument(); + expect(input).not.toBeDisabled(); + + // Should not show a "Reset" button + expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).not.toBeInTheDocument(); + }); + + it('should render a disabled input with a reset button if the secret is already configured', () => { + render( {}} onReset={() => {}} placeholder={PLACEHOLDER_TEXT} />); + + const input = screen.getByPlaceholderText(PLACEHOLDER_TEXT); + + // Should show a disabled input + expect(input).toBeInTheDocument(); + expect(input).toBeDisabled(); + expect(input).toHaveValue(CONFIGURED_TEXT); + + // Should show a reset button + expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument(); + }); + + it('should be possible to reset a configured secret', () => { + const onReset = jest.fn(); + + render( {}} onReset={onReset} placeholder={PLACEHOLDER_TEXT} />); + + // Should show a reset button and a disabled input + expect(screen.queryByPlaceholderText(PLACEHOLDER_TEXT)).toBeDisabled(); + expect(screen.queryByRole('button', { name: RESET_BUTTON_TEXT })).toBeInTheDocument(); + + // Click on "Reset" + userEvent.click(screen.getByRole('button', { name: RESET_BUTTON_TEXT })); + + expect(onReset).toHaveBeenCalledTimes(1); + }); + + it('should be possible to change the value of the secret', () => { + const onChange = jest.fn(); + + render( {}} placeholder={PLACEHOLDER_TEXT} />); + + const input = screen.getByPlaceholderText(PLACEHOLDER_TEXT); + + expect(input).toHaveValue(''); + + userEvent.type(input, 'Foo'); + + expect(onChange).toHaveBeenCalled(); + expect(input).toHaveValue('Foo'); + }); +}); diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx new file mode 100644 index 00000000000..d46a70903a1 --- /dev/null +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { Input } from '../Input/Input'; +import { HorizontalGroup } from '../Layout/Layout'; +import { Button } from '../Button'; + +export type Props = React.ComponentProps & { + /** TRUE if the secret was already configured. (It is needed as often the backend doesn't send back the actual secret, only the information that it was configured) */ + isConfigured: boolean; + /** Called when the user clicks on the "Reset" button in order to clear the secret */ + onReset: () => void; +}; + +export const CONFIGURED_TEXT = 'configured'; +export const RESET_BUTTON_TEXT = 'Reset'; + +export const SecretInput = ({ isConfigured, onReset, ...props }: Props) => ( + + {!isConfigured && } + {isConfigured && } + {isConfigured && ( + + )} + +); diff --git a/packages/grafana-ui/src/components/SecretInput/index.tsx b/packages/grafana-ui/src/components/SecretInput/index.tsx new file mode 100644 index 00000000000..ff621221c96 --- /dev/null +++ b/packages/grafana-ui/src/components/SecretInput/index.tsx @@ -0,0 +1 @@ +export { SecretInput } from './SecretInput';