diff --git a/.cypress/integration/integrations_test/integrations.spec.js b/.cypress/integration/integrations_test/integrations.spec.js index 28c816f5d8..38de3ec7c1 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 0b99a60d04..d9d702334d 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 0ed6e8e984..360060e564 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 db2286ad89..ee51138f0e 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 c6158f6f53..7610f0880a 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 5c210c6a01..61fe5d741e 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 621d5310fc..0ec0ef1cf4 100644 --- a/public/components/integrations/components/available_integration_card_view.tsx +++ b/public/components/integrations/components/available_integration_card_view.tsx @@ -24,9 +24,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 07f831603b..9d14608e25 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 e1baae54ca..57974aa7c5 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 1b342ea333..4bbf845aa4 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 ( @@ -86,15 +111,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) })} /> ) : ( <> @@ -105,7 +127,7 @@ export const IntegrationHeader = () => { - setShowUploadFlyout(true)} /> + setShowUploadFlyout(true)} /> @@ -117,7 +139,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 b346df4ea9..0d5d9a3219 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 6cdbcd5074..05e4cd463f 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 9fc9885019..0429af773e 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 57bddd8730..ada8dc6575 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} + ) + )} +
+ )} + + )} + + ); };