Skip to content

Commit

Permalink
Implement upload flyout for integrations (#1887)
Browse files Browse the repository at this point in the history
* Add catalog menu on integrations page

Signed-off-by: Simeon Widdis <[email protected]>

* Update check link to new constant

Signed-off-by: Simeon Widdis <[email protected]>

* Add integration bundle upload button

Signed-off-by: Simeon Widdis <[email protected]>

* Add tests for new functionality

Signed-off-by: Simeon Widdis <[email protected]>

---------

Signed-off-by: Simeon Widdis <[email protected]>
  • Loading branch information
Swiddis authored Jun 11, 2024
1 parent c49c6e8 commit 97958f5
Show file tree
Hide file tree
Showing 7 changed files with 468 additions and 7 deletions.
2 changes: 2 additions & 0 deletions common/constants/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const ASSET_FILTER_OPTIONS = [
'observability-search',
];
export const VALID_INDEX_NAME = /^[a-z\d\.][a-z\d\._\-\*]*$/;
export const OPENSEARCH_CATALOG_URL =
'https://github.com/opensearch-project/opensearch-catalog/releases';

// Upstream doesn't export this, so we need to redeclare it for our use.
export type Color = 'success' | 'primary' | 'warning' | 'danger' | undefined;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,124 @@ exports[`Integration Header Test Renders integration header as expected 1`] = `
</EuiTitle>
</div>
</EuiPageHeaderSection>
<EuiPageHeaderSection>
<div
className="euiPageHeaderSection"
>
<IntegrationHeaderActions
onShowUpload={[Function]}
>
<EuiPopover
anchorPosition="downLeft"
button={
<EuiButton
fill={true}
iconType="arrowDown"
onClick={[Function]}
>
Catalog
</EuiButton>
}
closePopover={[Function]}
display="inlineBlock"
hasArrow={true}
id="integHeaderActionsPanel"
isOpen={false}
ownFocus={true}
panelPaddingSize="none"
>
<div
className="euiPopover euiPopover--anchorDownLeft"
id="integHeaderActionsPanel"
>
<div
className="euiPopover__anchor"
>
<EuiButton
fill={true}
iconType="arrowDown"
onClick={[Function]}
>
<EuiButtonDisplay
baseClassName="euiButton"
disabled={false}
element="button"
fill={true}
iconType="arrowDown"
isDisabled={false}
onClick={[Function]}
type="button"
>
<button
className="euiButton euiButton--primary euiButton--fill"
disabled={false}
onClick={[Function]}
style={
Object {
"minWidth": undefined,
}
}
type="button"
>
<EuiButtonContent
className="euiButton__content"
iconSide="left"
iconType="arrowDown"
textProps={
Object {
"className": "euiButton__text",
}
}
>
<span
className="euiButtonContent euiButton__content"
>
<EuiIcon
className="euiButtonContent__icon"
color="inherit"
size="m"
type="arrowDown"
>
<EuiIconBeaker
aria-hidden={true}
className="euiIcon euiIcon--medium euiIcon--inherit euiIcon-isLoading euiButtonContent__icon"
focusable="false"
role="img"
style={null}
>
<svg
aria-hidden={true}
className="euiIcon euiIcon--medium euiIcon--inherit euiIcon-isLoading euiButtonContent__icon"
focusable="false"
height={16}
role="img"
style={null}
viewBox="0 0 16 16"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5.277 10.088c.02.014.04.03.057.047.582.55 1.134.812 1.666.812.586 0 1.84-.293 3.713-.88L9 6.212V2H7v4.212l-1.723 3.876Zm-.438.987L3.539 14h8.922l-1.32-2.969C9.096 11.677 7.733 12 7 12c-.74 0-1.463-.315-2.161-.925ZM6 2H5V1h6v1h-1v4l3.375 7.594A1 1 0 0 1 12.461 15H3.54a1 1 0 0 1-.914-1.406L6 6V2Z"
/>
</svg>
</EuiIconBeaker>
</EuiIcon>
<span
className="euiButton__text"
>
Catalog
</span>
</span>
</EuiButtonContent>
</button>
</EuiButtonDisplay>
</EuiButton>
</div>
</div>
</EuiPopover>
</IntegrationHeaderActions>
</div>
</EuiPageHeaderSection>
</header>
</EuiPageHeader>
<EuiSpacer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Integration Upload Flyout Renders integration upload flyout as expected 1`] = `
<EuiFlyout
onClose={[Function]}
size="s"
>
<EuiFlyoutHeader>
Upload Integrations
</EuiFlyoutHeader>
<EuiFlyoutBody>
<IntegrationUploadPicker
isInvalid={true}
onFileSelected={[Function]}
setIsInvalid={[Function]}
/>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup
justifyContent="spaceBetween"
>
<EuiFlexItem
grow={false}
>
<EuiButton
onClick={[Function]}
>
Cancel
</EuiButton>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiButton
disabled={true}
fill={true}
onClick={[Function]}
>
Upload
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
`;

exports[`Integration Upload Flyout Renders the clean integration picker as expected 1`] = `
<EuiForm>
<EuiFormRow
describedByIds={Array []}
display="row"
error={
Array [
"Must be an ndjson bundle of integration templates",
]
}
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
isInvalid={false}
label="Select file"
labelType="label"
>
<EuiFilePicker
compressed={false}
display="large"
id="integrationBundlePicker"
initialPromptText="Select or drag and drop integration bundles"
isInvalid={false}
onBlur={[Function]}
onChange={[Function]}
/>
</EuiFormRow>
</EuiForm>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { configure, shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import React from 'react';
import { waitFor } from '@testing-library/react';
import { IntegrationUploadFlyout, IntegrationUploadPicker } from '../upload_flyout';

describe('Integration Upload Flyout', () => {
configure({ adapter: new Adapter() });

it('Renders integration upload flyout as expected', async () => {
const wrapper = shallow(<IntegrationUploadFlyout onClose={() => {}} />);

await waitFor(() => {
expect(wrapper).toMatchSnapshot();
});
});

it('Renders the clean integration picker as expected', async () => {
const wrapper = shallow(
<IntegrationUploadPicker
isInvalid={true}
setIsInvalid={(_) => {}}
onFileSelected={(_) => {}}
/>
);

await waitFor(() => {
expect(wrapper).toMatchSnapshot();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
EuiTitle,
} from '@elastic/eui';
import React from 'react';
import { OPENSEARCH_CATALOG_URL } from '../../../../common/constants/integrations';

export function IntegrationDetails(props: { integration: IntegrationConfig }) {
const config = props.integration;
Expand Down Expand Up @@ -48,9 +49,7 @@ export function IntegrationDetails(props: { integration: IntegrationConfig }) {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs">
<EuiLink href="https://github.com/opensearch-project/opensearch-catalog/releases">
Check for new versions
</EuiLink>
<EuiLink href={OPENSEARCH_CATALOG_URL}>Check for new versions</EuiLink>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
65 changes: 61 additions & 4 deletions public/components/integrations/components/integration_header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,68 @@
*/

import {
EuiButton,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiLink,
EuiPageHeader,
EuiPageHeaderSection,
EuiPopover,
EuiSpacer,
EuiTab,
EuiTabs,
EuiText,
EuiTitle,
} from '@elastic/eui';
import React, { useState } from 'react';
import { OPENSEARCH_DOCUMENTATION_URL } from '../../../../common/constants/integrations';
import {
OPENSEARCH_CATALOG_URL,
OPENSEARCH_DOCUMENTATION_URL,
} from '../../../../common/constants/integrations';
import { IntegrationUploadFlyout } from './upload_flyout';

export const IntegrationHeaderActions = ({ onShowUpload }: { onShowUpload: () => void }) => {
const [isPopoverOpen, setPopover] = useState(false);

const closePopover = () => {
setPopover(false);
};

const onButtonClick = () => {
setPopover((isOpen) => !isOpen);
};

const items = [
<EuiContextMenuItem
onClick={() => {
closePopover(); // If the popover isn't closed, it overlays over the flyout
onShowUpload();
}}
>
Upload Integrations
</EuiContextMenuItem>,
<EuiContextMenuItem href={OPENSEARCH_CATALOG_URL}>View Catalog</EuiContextMenuItem>,
];
const button = (
<EuiButton iconType="arrowDown" fill={true} onClick={onButtonClick}>
Catalog
</EuiButton>
);
return (
<EuiPopover
id="integHeaderActionsPanel"
button={button}
isOpen={isPopoverOpen}
closePopover={closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel items={items} />
</EuiPopover>
);
};

export function IntegrationHeader() {
export const IntegrationHeader = () => {
const tabs = [
{
id: 'installed',
Expand All @@ -33,8 +82,9 @@ export function IntegrationHeader() {
const [selectedTabId, setSelectedTabId] = useState(
window.location.hash.substring(2) ? window.location.hash.substring(2) : 'installed'
);
const [showUploadFlyout, setShowUploadFlyout] = useState(false);

const onSelectedTabChanged = (id) => {
const onSelectedTabChanged = (id: string) => {
setSelectedTabId(id);
window.location.hash = id;
};
Expand All @@ -51,6 +101,7 @@ export function IntegrationHeader() {
</EuiTab>
));
};

return (
<div>
<EuiPageHeader>
Expand All @@ -59,6 +110,9 @@ export function IntegrationHeader() {
<h1>Integrations</h1>
</EuiTitle>
</EuiPageHeaderSection>
<EuiPageHeaderSection>
<IntegrationHeaderActions onShowUpload={() => setShowUploadFlyout(true)} />
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
Expand All @@ -70,6 +124,9 @@ export function IntegrationHeader() {
<EuiSpacer size="l" />
<EuiTabs display="condensed">{renderTabs()}</EuiTabs>
<EuiSpacer size="s" />
{showUploadFlyout ? (
<IntegrationUploadFlyout onClose={() => setShowUploadFlyout(false)} />
) : null}
</div>
);
}
};
Loading

0 comments on commit 97958f5

Please sign in to comment.