From 929d67db0dca9f2ff5aee25d2eda3ca7df68a760 Mon Sep 17 00:00:00 2001 From: Adam Tackett <105462877+TackAdam@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:18:10 -0700 Subject: [PATCH] HeaderControl update style, UI Adjustments Integrations (#2171) * UI refactor, adjust headercontrolls Signed-off-by: Adam Tackett * comments addressed, ui final feedback Signed-off-by: Adam Tackett * update name Signed-off-by: Adam Tackett --------- Signed-off-by: Adam Tackett Co-authored-by: Adam Tackett --- .../integrations_test/integrations.spec.js | 2 +- .../components/app_table.tsx | 34 +- .../getting_started_integrationCards.tsx | 3 +- .../added_integration.test.tsx.snap | 239 ++++---- .../added_integration_table.test.tsx.snap | 566 ++++++++++-------- ...ilable_integration_card_view.test.tsx.snap | 12 - .../integration_details.test.tsx.snap | 119 ++-- .../integration_fields.test.tsx.snap | 10 +- .../integration_header.test.tsx.snap | 8 +- .../components/added_integration.tsx | 49 +- .../components/added_integration_table.tsx | 113 ++-- .../available_integration_card_view.tsx | 4 +- .../integrations/components/integration.tsx | 2 +- .../components/integration_assets_panel.tsx | 4 +- .../components/integration_details_panel.tsx | 45 +- .../components/integration_fields_panel.tsx | 4 +- .../components/integration_header.tsx | 46 +- .../integration_screenshots_panel.tsx | 4 +- .../notebooks/components/note_table.tsx | 17 +- public/index.scss | 5 + .../plugin_helpers/plugin_headerControl.tsx | 168 ++++-- 21 files changed, 807 insertions(+), 647 deletions(-) diff --git a/.cypress/integration/integrations_test/integrations.spec.js b/.cypress/integration/integrations_test/integrations.spec.js index 28c816f5d..38de3ec7c 100644 --- a/.cypress/integration/integrations_test/integrations.spec.js +++ b/.cypress/integration/integrations_test/integrations.spec.js @@ -107,7 +107,7 @@ describe('Add nginx integration instance flow', () => { cy.get('button[data-test-subj="popoverModal__deleteButton"]').should('be.disabled'); - cy.get(`input.euiFieldText[placeholder="${testInstance}"]`).focus().type(testInstance, { + cy.get(`input.euiFieldText[placeholder="delete"]`).focus().type("delete", { delay: 50, }); cy.get('button[data-test-subj="popoverModal__deleteButton"]').should('not.be.disabled'); diff --git a/public/components/application_analytics/components/app_table.tsx b/public/components/application_analytics/components/app_table.tsx index 0b99a60d0..d9d702334 100644 --- a/public/components/application_analytics/components/app_table.tsx +++ b/public/components/application_analytics/components/app_table.tsx @@ -40,6 +40,7 @@ import { setNavBreadCrumbs } from '../../../../common/utils/set_nav_bread_crumbs import { DeleteModal } from '../../common/helpers/delete_modal'; import { HeaderControlledComponentsWrapper } from '../../../../public/plugin_helpers/plugin_headerControl'; import { coreRefs } from '../../../framework/core_refs'; +import { TopNavControlButtonData } from '../../../../../../src/plugins/navigation/public'; const newNavigation = coreRefs.chrome?.navGroup.getNavGroupEnabled(); @@ -234,6 +235,30 @@ export function AppTable(props: AppTableProps) {

Applications {` (${applications.length})`}

)} + + { + window.location.href = '#/create'; + }, + iconType: 'plus', + iconSide: 'left', + fill: true, + controlType: 'button', + } as TopNavControlButtonData, + ] + : [ + + {createButtonText} + , + ] + } + /> + @@ -249,15 +274,6 @@ export function AppTable(props: AppTableProps) { aria-label="Search applications" /> - - - {createButtonText} - , - ]} - /> - {filteredApplications.length > 0 ? ( diff --git a/public/components/getting_started/components/getting_started_integrationCards.tsx b/public/components/getting_started/components/getting_started_integrationCards.tsx index 0ed6e8e98..360060e56 100644 --- a/public/components/getting_started/components/getting_started_integrationCards.tsx +++ b/public/components/getting_started/components/getting_started_integrationCards.tsx @@ -136,13 +136,12 @@ export const IntegrationCards = () => { icon={ integration.statics && integration.statics.logo && integration.statics.logo.path ? ( ) : ( -
+
) } title={integration.displayName || integration.name} diff --git a/public/components/integrations/components/__tests__/__snapshots__/added_integration.test.tsx.snap b/public/components/integrations/components/__tests__/__snapshots__/added_integration.test.tsx.snap index db2286ad8..ee51138f0 100644 --- a/public/components/integrations/components/__tests__/__snapshots__/added_integration.test.tsx.snap +++ b/public/components/integrations/components/__tests__/__snapshots__/added_integration.test.tsx.snap @@ -219,63 +219,90 @@ exports[`Added Integration View Test Renders added integration view using dummy
- + } + delay="regular" + position="top" > - - - - + + + + + + + + + + +
@@ -286,58 +313,68 @@ exports[`Added Integration View Test Renders added integration view using dummy - +
- +
- +
-

- Template -

+ +
+

+ Template +

+
+
+ + +
-
- - - -
-
- -
- + +
-

- Date Added -

+ +
+

+ Date Added +

+
+
+ +
+
-
- -
- +
- +
- + @@ -349,28 +386,20 @@ exports[`Added Integration View Test Renders added integration view using dummy
- - -
- - Assets List - -
-
-
+ Assets List + +
@@ -129,13 +130,6 @@ exports[`Added Integration Table View Test Renders added integration table view "sortable": true, "truncateText": true, }, - Object { - "field": "actions", - "name": "Actions", - "render": [Function], - "sortable": true, - "truncateText": true, - }, ] } isSelectable={true} @@ -189,6 +183,12 @@ exports[`Added Integration Table View Test Renders added integration table view "type": "field_value_selection", }, ], + "toolsLeft": false, + } + } + selection={ + Object { + "onSelectionChange": [Function], } } tableLayout="auto" @@ -220,6 +220,7 @@ exports[`Added Integration Table View Test Renders added integration table view ] } onChange={[Function]} + toolsLeft={false} >
+ > + + +
+ +
+ +
+ + +
+ + +
+ + +
+ +
+
+ + +
+ + - - - - - - - - Actions - - - - - - - @@ -888,6 +933,54 @@ exports[`Added Integration Table View Test Renders added integration table view + + +
+ + +
+ +
+
+ + +
+ + - - -
- Actions -
-
- - - - - - - -
- -
@@ -1607,15 +1636,16 @@ exports[`Added Integration Table View Test Renders added integration table view >
@@ -1644,13 +1674,6 @@ exports[`Added Integration Table View Test Renders added integration table view "sortable": true, "truncateText": true, }, - Object { - "field": "actions", - "name": "Actions", - "render": [Function], - "sortable": true, - "truncateText": true, - }, ] } isSelectable={true} @@ -1704,6 +1727,12 @@ exports[`Added Integration Table View Test Renders added integration table view "type": "field_value_selection", }, ], + "toolsLeft": false, + } + } + selection={ + Object { + "onSelectionChange": [Function], } } tableLayout="auto" @@ -1735,6 +1764,7 @@ exports[`Added Integration Table View Test Renders added integration table view ] } onChange={[Function]} + toolsLeft={false} >
+ > + + +
+ +
+ +
+ + +
+ + +
+ + +
+ +
+
+ + +
+ + - - - - - - - - Actions - - - - - - - @@ -2404,6 +2478,54 @@ exports[`Added Integration Table View Test Renders added integration table view + + +
+ + +
+ +
+
+ + +
+ + - - -
- Actions -
-
- - - - - - - -
- -
diff --git a/public/components/integrations/components/__tests__/__snapshots__/available_integration_card_view.test.tsx.snap b/public/components/integrations/components/__tests__/__snapshots__/available_integration_card_view.test.tsx.snap index c6158f6f5..7610f0880 100644 --- a/public/components/integrations/components/__tests__/__snapshots__/available_integration_card_view.test.tsx.snap +++ b/public/components/integrations/components/__tests__/__snapshots__/available_integration_card_view.test.tsx.snap @@ -512,12 +512,6 @@ exports[`Available Integration Card View Test Renders nginx integration card vie alt="" className="synopsisIcon" src="/api/integrations/repository/nginx/static/logo.svg" - style={ - Object { - "height": 100, - "width": 100, - } - } /> } title="NginX Dashboard" @@ -542,12 +536,6 @@ exports[`Available Integration Card View Test Renders nginx integration card vie alt="" className="synopsisIcon euiCard__icon" src="/api/integrations/repository/nginx/static/logo.svg" - style={ - Object { - "height": 100, - "width": 100, - } - } />
- -

- Details -

-
@@ -30,7 +23,9 @@ exports[`Available Integration Table View Test Renders nginx integration table v
@@ -120,7 +95,9 @@ exports[`Available Integration Table View Test Renders nginx integration table v
- +
@@ -140,12 +117,14 @@ exports[`Available Integration Table View Test Renders nginx integration table v
- +

- Contributer + Contributor

@@ -209,7 +188,9 @@ exports[`Available Integration Table View Test Renders nginx integration table v
- +
@@ -235,7 +216,9 @@ exports[`Available Integration Table View Test Renders nginx integration table v
- +
diff --git a/public/components/integrations/components/__tests__/__snapshots__/integration_fields.test.tsx.snap b/public/components/integrations/components/__tests__/__snapshots__/integration_fields.test.tsx.snap index 5c210c6a0..61fe5d741 100644 --- a/public/components/integrations/components/__tests__/__snapshots__/integration_fields.test.tsx.snap +++ b/public/components/integrations/components/__tests__/__snapshots__/integration_fields.test.tsx.snap @@ -8,12 +8,14 @@ exports[`Available Integration Table View Test Renders nginx integration table v className="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow" data-test-subj="nginx-fields" > - -

+

Fields -

+
-
- +
@@ -233,11 +233,11 @@ exports[`Integration Header Test Renders integration header as expected 1`] = ` />
(); - const activateDeleteModal = (integrationName?: string) => { + const activateDeleteModal = () => { setModalLayout( { @@ -101,7 +103,6 @@ export function AddedIntegration(props: AddedIntegrationProps) { }} title={`Delete Integration`} message={`Are you sure you want to delete the selected Integration?`} - prompt={integrationName} /> ); setIsModalVisible(true); @@ -134,15 +135,25 @@ export function AddedIntegration(props: AddedIntegrationProps) { const badgeContent = ; const deleteButton = ( - { - activateDeleteModal(data?.name); - }} - data-test-subj="deleteInstanceButton" - /> + + } + > + { + activateDeleteModal(data?.name); + }} + data-test-subj="deleteInstanceButton" + /> + ); const headerContent = ( @@ -166,13 +177,13 @@ export function AddedIntegration(props: AddedIntegrationProps) { const additionalInfo = ( - +

Template

{data?.templateName}
- +

Date Added

{data?.creationDate?.split('T')[0]} @@ -206,7 +217,7 @@ export function AddedIntegration(props: AddedIntegrationProps) { deleteButton, ]} /> - {additionalInfo} + {additionalInfo} ) : ( <> @@ -231,7 +242,7 @@ export function AddedIntegration(props: AddedIntegrationProps) { {headerContent} - {additionalInfo} + {additionalInfo} ); } @@ -356,8 +367,10 @@ export function AddedIntegration(props: AddedIntegrationProps) { return ( - - + +

Assets List

+
+ ); + const [selectedIntegrations, setSelectedIntegrations] = useState([]); const tableColumns = [ { @@ -70,20 +68,6 @@ export function AddedIntegrationsTable(props: AddedIntegrationsTableProps) {
), }, - { - field: 'actions', - name: 'Actions', - sortable: true, - truncateText: true, - render: (value, record) => ( - { - activateDeleteModal(record.id, record.name); - }} - /> - ), - }, ] as Array>; if (dataSourceEnabled) { @@ -100,36 +84,38 @@ export function AddedIntegrationsTable(props: AddedIntegrationsTableProps) { }); } - async function deleteAddedIntegration(integrationInstance: string, name: string) { - http - .delete(`${INTEGRATIONS_BASE}/store/${integrationInstance}`) - .then(() => { - setToast(`${name} integration successfully deleted!`, 'success'); - props.setData({ - hits: props.data.hits.filter((i) => i.id !== integrationInstance), - }); - }) - .catch((_err) => { - setToast(`Error deleting ${name} or its assets`, 'danger'); - }) - .finally(() => { - window.location.hash = '#/installed'; - }); + async function deleteAddedIntegrations(selectedItems: any[]) { + const deletePromises = selectedItems.map(async (item) => { + try { + await http.delete(`${INTEGRATIONS_BASE}/store/${item.id}`); + setToast(`${item.name} integration successfully deleted!`, 'success'); + } catch (error) { + setToast(`Error deleting ${item.name} or its assets`, 'danger'); + } + }); + + Promise.all(deletePromises); + + props.setData({ + hits: props.data.hits.filter( + (item) => !selectedItems.some((selected) => selected.id === item.id) + ), + }); + + window.location.hash = '#/installed'; } - const activateDeleteModal = (integrationInstanceId: string, name: string) => { + const activateDeleteModal = () => { + const integrationNames = selectedIntegrations.map((item) => item.name).join(', '); setModalLayout( { + onConfirm={async () => { setIsModalVisible(false); - deleteAddedIntegration(integrationInstanceId, name); + await deleteAddedIntegrations(selectedIntegrations); }} - onCancel={() => { - setIsModalVisible(false); - }} - title={`Delete Integration`} - message={`Are you sure you want to delete the selected integration?`} - prompt={name} + onCancel={() => setIsModalVisible(false)} + title={`Delete Integrations`} + message={`Are you sure you want to delete the selected integrations: ${integrationNames}?`} /> ); setIsModalVisible(true); @@ -147,6 +133,17 @@ export function AddedIntegrationsTable(props: AddedIntegrationsTableProps) { } const search = { + toolsLeft: selectedIntegrations.length > 0 && ( + + Delete {selectedIntegrations.length} integration + {selectedIntegrations.length > 1 ? 's' : ''} + + ), box: { incremental: true, compressed: true, @@ -203,7 +200,7 @@ export function AddedIntegrationsTable(props: AddedIntegrationsTableProps) { }); return ( - + {entries && entries.length > 0 ? ( setSelectedIntegrations(items), + }} /> ) : ( - <> - - - - - - - -

- There are currently no added integrations. Add them{' '} - here to start using pre-canned assets! -

-
- - + No installed integrations} + body={ +

+ There are currently no added integrations in this table. Add integrations from the{' '} + available list. +

+ } + /> )} {isModalVisible && modalLayout}
diff --git a/public/components/integrations/components/available_integration_card_view.tsx b/public/components/integrations/components/available_integration_card_view.tsx index 346719f38..1c5978ccc 100644 --- a/public/components/integrations/components/available_integration_card_view.tsx +++ b/public/components/integrations/components/available_integration_card_view.tsx @@ -27,9 +27,7 @@ export function AvailableIntegrationsCardView(props: AvailableIntegrationsCardVi const getImage = (url?: string) => { let optionalImg; if (url) { - optionalImg = ( - - ); + optionalImg = ; } return optionalImg; }; diff --git a/public/components/integrations/components/integration.tsx b/public/components/integrations/components/integration.tsx index 07f831603..9d14608e2 100644 --- a/public/components/integrations/components/integration.tsx +++ b/public/components/integrations/components/integration.tsx @@ -256,7 +256,7 @@ export function Integration(props: AvailableIntegrationProps) { {IntegrationScreenshots({ integration, http })} - + {renderTabs()} diff --git a/public/components/integrations/components/integration_assets_panel.tsx b/public/components/integrations/components/integration_assets_panel.tsx index eafd6683d..3035413b9 100644 --- a/public/components/integrations/components/integration_assets_panel.tsx +++ b/public/components/integrations/components/integration_assets_panel.tsx @@ -85,8 +85,8 @@ export function IntegrationAssets(props: { return ( - -

Assets

+ +

Assets

- -

Details

-
- +

Version

- {/* - For the link, we have the slightly odd constraint to have it go to the end of the version - space while being horizontally next to the version (i.e. no direct EuiText). It should be - smaller, while aligning to the bottom of the line, but not the bottom of the entire flex - area, for a nice subscript effect. - - The end result is a bit of flex magic: make two vertical boxes with the second one empty - and growing, then in the top one put two horizontal boxes with space-between, aligning to - the bottom. - */} - + + + {config.version} + - - - {config.version} - - - - Check for new versions - - - + + Check for new versions + -
- +

Category

@@ -68,8 +49,8 @@ export function IntegrationDetails(props: { integration: IntegrationConfig }) {
- -

Contributer

+ +

Contributor

@@ -78,14 +59,14 @@ export function IntegrationDetails(props: { integration: IntegrationConfig }) {
- +

License

{config.license}
- +

Description

{config.description} diff --git a/public/components/integrations/components/integration_fields_panel.tsx b/public/components/integrations/components/integration_fields_panel.tsx index daca71093..4ba1cab6d 100644 --- a/public/components/integrations/components/integration_fields_panel.tsx +++ b/public/components/integrations/components/integration_fields_panel.tsx @@ -94,8 +94,8 @@ export function IntegrationFields(props: any) { return ( - -

Fields

+ +

Fields

void }) => { +export const IntegrationHeaderActions = ({ + onShowUpload, +}: { + onShowUpload: () => void; +}): Array => { + return [ + { + label: 'View Catalog', + href: OPENSEARCH_CATALOG_URL, + target: '_blank', + controlType: 'link', + } as TopNavControlLinkData, + { + label: 'Upload Integration', + run: onShowUpload, + fill: true, + controlType: 'button', + } as TopNavControlButtonData, + ]; +}; + +export const IntegrationHeaderActionsOldNav = ({ onShowUpload }: { onShowUpload: () => void }) => { return ( @@ -85,15 +110,12 @@ export const IntegrationHeader = () => {
{newNavigation ? ( - View integrations with preconfigured assets immediately within your OpenSearch setup.{' '} - - Learn more - - - } - components={[ setShowUploadFlyout(true)} />]} + description={{ + text: + 'View integrations with preconfigured assets immediately within your OpenSearch setup.', + url: OPENSEARCH_DOCUMENTATION_URL, + }} + components={IntegrationHeaderActions({ onShowUpload: () => setShowUploadFlyout(true) })} /> ) : ( <> @@ -104,7 +126,7 @@ export const IntegrationHeader = () => { - setShowUploadFlyout(true)} /> + setShowUploadFlyout(true)} /> @@ -116,7 +138,7 @@ export const IntegrationHeader = () => { )} {!newNavigation && } - + {renderTabs()} diff --git a/public/components/integrations/components/integration_screenshots_panel.tsx b/public/components/integrations/components/integration_screenshots_panel.tsx index b346df4ea..0d5d9a321 100644 --- a/public/components/integrations/components/integration_screenshots_panel.tsx +++ b/public/components/integrations/components/integration_screenshots_panel.tsx @@ -17,8 +17,8 @@ export function IntegrationScreenshots(props: any) { return ( - -

Screenshots

+ +

Screenshots

diff --git a/public/components/notebooks/components/note_table.tsx b/public/components/notebooks/components/note_table.tsx index 003d30725..12d031fac 100644 --- a/public/components/notebooks/components/note_table.tsx +++ b/public/components/notebooks/components/note_table.tsx @@ -241,17 +241,12 @@ export function NoteTable({ {newNavigation ? ( - Use Notebooks to interactively and collaboratively develop rich reports backed - by live data. Common use cases for notebooks include creating postmortem - reports, designing run books, building live infrastructure reports, or even - documentation.{' '} - - Learn more - - - } + description={{ + text: + 'Use Notebooks to interactively and collaboratively develop rich reports backed by live data. Common use cases for notebooks include creating postmortem reports, designing run books, building live infrastructure reports, or even documentation.', + url: NOTEBOOKS_DOCUMENTATION_URL, + urlTitle: 'Learn more', + }} components={[ diff --git a/public/index.scss b/public/index.scss index 9fc988501..0429af773 100644 --- a/public/index.scss +++ b/public/index.scss @@ -11,3 +11,8 @@ // event analytics @import 'components/event_analytics/index'; + +.synopsisIcon { + height: 40px; + width: 40px; + } \ No newline at end of file diff --git a/public/plugin_helpers/plugin_headerControl.tsx b/public/plugin_helpers/plugin_headerControl.tsx index 57bddd873..ada8dc657 100644 --- a/public/plugin_helpers/plugin_headerControl.tsx +++ b/public/plugin_helpers/plugin_headerControl.tsx @@ -4,15 +4,60 @@ */ import React from 'react'; -import { EuiText } from '@elastic/eui'; import { coreRefs } from '../framework/core_refs'; +import { + TopNavControlLinkData, + TopNavControlButtonData, +} from '../../../../src/plugins/navigation/public'; + +interface DescriptionWithOptionalLink { + text: string; + url?: string; + urlTitle?: string; +} interface HeaderControlledComponentsWrapperProps { - components?: React.ReactElement[]; + components?: Array; badgeContent?: React.ReactElement | string | number; - description?: React.ReactNode; + description?: string | DescriptionWithOptionalLink; } +const renderHeaderComponent = ( + component: TopNavControlButtonData | TopNavControlLinkData | React.ReactElement +) => { + if (React.isValidElement(component)) { + return { + renderComponent: component, + }; + } + + switch ((component as TopNavControlButtonData | TopNavControlLinkData).controlType) { + case 'button': { + const buttonData = component as TopNavControlButtonData; + return { + label: buttonData.label, + run: buttonData.run, + fill: buttonData.fill, + color: buttonData.color, + iconType: buttonData.iconType, + iconSide: buttonData.iconSide, + controlType: 'button', + }; + } + case 'link': { + const linkData = component as TopNavControlLinkData; + return { + label: linkData.label, + href: linkData.href, + target: linkData.target, + controlType: 'link', + }; + } + default: + return {}; + } +}; + export const HeaderControlledComponentsWrapper = ({ components = [], badgeContent, @@ -23,48 +68,79 @@ export const HeaderControlledComponentsWrapper = ({ const isBadgeReactElement = React.isValidElement(badgeContent); - if (showActionsInHeader && HeaderControl) { - return ( - <> - {badgeContent && ( - {badgeContent} - ) : ( - {`(${badgeContent})`} - ), // Render based on type - }, - ]} - /> - )} - {description && ( - {description}
, - }, - ]} - /> - )} - {components.length > 0 && ( - ({ - key: `header-control-${index}`, - renderComponent: component, - }))} - /> - )} - - ); - } + return ( + <> + {badgeContent && ( + <> + {showActionsInHeader && HeaderControl ? ( + {badgeContent} + ) : ( + {`(${badgeContent})`} + ), + }, + ]} + /> + ) : ( + {isBadgeReactElement ? badgeContent : `(${badgeContent})`} + )} + + )} - // Only render the components if the nav group is disabled - return <>{components}; + {description && ( + <> + {showActionsInHeader && HeaderControl ? ( + + ) : ( +

{typeof description === 'string' ? description : description.text}

+ )} + + )} + + {components.length > 0 && ( + <> + {showActionsInHeader && HeaderControl ? ( + renderHeaderComponent(component))} + /> + ) : ( +
+ {components.map((component) => + React.isValidElement(component) ? ( + {component} + ) : ( + {(component as TopNavControlButtonData).label} + ) + )} +
+ )} + + )} + + ); };