mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[8.16] [Security Solution] [Onboarding] t1_analyst role blocked from interacting with cards due to missing integration privileges (#202413) (#202643)
# Backport This will backport the following commits from `main` to `8.16`: - [[Security Solution] [Onboarding] t1_analyst role blocked from interacting with cards due to missing integration privileges (#202413)](https://github.com/elastic/kibana/pull/202413) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Agustina Nahir Ruidiaz","email":"61565784+agusruidiazgd@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-12-02T14:23:56Z","message":"[Security Solution] [Onboarding] t1_analyst role blocked from interacting with cards due to missing integration privileges (#202413)\n\n## Summary\r\n\r\nThis PR temporarily fixes the #201799 issue for Serverless.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/604128cb-49b0-4a93-9a15-2a5a0c511883\r\n\r\n\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"3daaaa5b8c3e3a47b1e29f75df301b42d89caa87","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Threat Hunting:Explore","ci:cloud-deploy","backport:version","v8.18.0"],"number":202413,"url":"https://github.com/elastic/kibana/pull/202413","mergeCommit":{"message":"[Security Solution] [Onboarding] t1_analyst role blocked from interacting with cards due to missing integration privileges (#202413)\n\n## Summary\r\n\r\nThis PR temporarily fixes the #201799 issue for Serverless.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/604128cb-49b0-4a93-9a15-2a5a0c511883\r\n\r\n\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"3daaaa5b8c3e3a47b1e29f75df301b42d89caa87"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/202413","number":202413,"mergeCommit":{"message":"[Security Solution] [Onboarding] t1_analyst role blocked from interacting with cards due to missing integration privileges (#202413)\n\n## Summary\r\n\r\nThis PR temporarily fixes the #201799 issue for Serverless.\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/604128cb-49b0-4a93-9a15-2a5a0c511883\r\n\r\n\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following conditions. \r\n\r\nReviewers should verify this PR satisfies this list as well.\r\n\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"3daaaa5b8c3e3a47b1e29f75df301b42d89caa87"}},{"branch":"8.x","label":"v8.18.0","labelRegex":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
This commit is contained in:
parent
38388ffed9
commit
048f45474c
11 changed files with 145 additions and 17 deletions
|
@ -14,6 +14,7 @@ const props = {
|
|||
checkComplete: jest.fn(),
|
||||
isCardComplete: jest.fn(),
|
||||
setExpandedCardId: jest.fn(),
|
||||
isCardAvailable: jest.fn(),
|
||||
};
|
||||
|
||||
describe('AlertsCard', () => {
|
||||
|
@ -31,7 +32,8 @@ describe('AlertsCard', () => {
|
|||
expect(getByTestId('alertsCardDescription')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('card callout should be rendered if integrations cards is not complete', () => {
|
||||
it('card callout should be rendered if integrations card is available but not complete', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(true);
|
||||
props.isCardComplete.mockReturnValueOnce(false);
|
||||
|
||||
const { getByText } = render(
|
||||
|
@ -43,7 +45,20 @@ describe('AlertsCard', () => {
|
|||
expect(getByText('To view alerts add integrations first.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('card button should be disabled if integrations cards is not complete', () => {
|
||||
it('card callout should not be rendered if integrations card is not available', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(false);
|
||||
|
||||
const { queryByText } = render(
|
||||
<TestProviders>
|
||||
<AlertsCard {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(queryByText('To view alerts add integrations first.')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('card button should be disabled if integrations card is available but not complete', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(true);
|
||||
props.isCardComplete.mockReturnValueOnce(false);
|
||||
|
||||
const { getByTestId } = render(
|
||||
|
@ -54,4 +69,17 @@ describe('AlertsCard', () => {
|
|||
|
||||
expect(getByTestId('alertsCardButton').querySelector('button')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('card button should be enabled if integrations card is complete', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(true);
|
||||
props.isCardComplete.mockReturnValueOnce(true);
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<AlertsCard {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(getByTestId('alertsCardButton').querySelector('button')).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,12 +20,18 @@ export const AlertsCard: OnboardingCardComponent = ({
|
|||
isCardComplete,
|
||||
setExpandedCardId,
|
||||
setComplete,
|
||||
isCardAvailable,
|
||||
}) => {
|
||||
const isIntegrationsCardComplete = useMemo(
|
||||
() => isCardComplete(OnboardingCardId.integrations),
|
||||
[isCardComplete]
|
||||
);
|
||||
|
||||
const isIntegrationsCardAvailable = useMemo(
|
||||
() => isCardAvailable(OnboardingCardId.integrations),
|
||||
[isCardAvailable]
|
||||
);
|
||||
|
||||
const expandIntegrationsCard = useCallback(() => {
|
||||
setExpandedCardId(OnboardingCardId.integrations, { scroll: true });
|
||||
}, [setExpandedCardId]);
|
||||
|
@ -42,7 +48,7 @@ export const AlertsCard: OnboardingCardComponent = ({
|
|||
<EuiText data-test-subj="alertsCardDescription" size="s" color="subdued">
|
||||
{i18n.ALERTS_CARD_DESCRIPTION}
|
||||
</EuiText>
|
||||
{!isIntegrationsCardComplete && (
|
||||
{isIntegrationsCardAvailable && !isIntegrationsCardComplete && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<CardCallOut
|
||||
|
@ -68,7 +74,7 @@ export const AlertsCard: OnboardingCardComponent = ({
|
|||
onClick={() => setComplete(true)}
|
||||
deepLinkId={SecurityPageName.alerts}
|
||||
fill
|
||||
isDisabled={!isIntegrationsCardComplete}
|
||||
isDisabled={isIntegrationsCardAvailable && !isIntegrationsCardComplete}
|
||||
>
|
||||
{i18n.ALERTS_CARD_VIEW_ALERTS_BUTTON}
|
||||
</SecuritySolutionLinkButton>
|
||||
|
|
|
@ -21,12 +21,18 @@ export const AssistantCard: OnboardingCardComponent<AssistantCardMetadata> = ({
|
|||
setExpandedCardId,
|
||||
checkCompleteMetadata,
|
||||
checkComplete,
|
||||
isCardAvailable,
|
||||
}) => {
|
||||
const isIntegrationsCardComplete = useMemo(
|
||||
() => isCardComplete(OnboardingCardId.integrations),
|
||||
[isCardComplete]
|
||||
);
|
||||
|
||||
const isIntegrationsCardAvailable = useMemo(
|
||||
() => isCardAvailable(OnboardingCardId.integrations),
|
||||
[isCardAvailable]
|
||||
);
|
||||
|
||||
const expandIntegrationsCard = useCallback(() => {
|
||||
setExpandedCardId(OnboardingCardId.integrations, { scroll: true });
|
||||
}, [setExpandedCardId]);
|
||||
|
@ -42,9 +48,7 @@ export const AssistantCard: OnboardingCardComponent<AssistantCardMetadata> = ({
|
|||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{isIntegrationsCardComplete ? (
|
||||
<ConnectorCards connectors={connectors} onConnectorSaved={checkComplete} />
|
||||
) : (
|
||||
{isIntegrationsCardAvailable && !isIntegrationsCardComplete ? (
|
||||
<EuiFlexItem
|
||||
className={css`
|
||||
width: 45%;
|
||||
|
@ -66,6 +70,8 @@ export const AssistantCard: OnboardingCardComponent<AssistantCardMetadata> = ({
|
|||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : (
|
||||
<ConnectorCards connectors={connectors} onConnectorSaved={checkComplete} />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -14,6 +14,7 @@ const props = {
|
|||
checkComplete: jest.fn(),
|
||||
isCardComplete: jest.fn(),
|
||||
setExpandedCardId: jest.fn(),
|
||||
isCardAvailable: jest.fn(),
|
||||
};
|
||||
|
||||
describe('RulesCard', () => {
|
||||
|
|
|
@ -17,9 +17,10 @@ const props = {
|
|||
checkComplete: jest.fn(),
|
||||
isCardComplete: jest.fn(),
|
||||
setExpandedCardId: jest.fn(),
|
||||
isCardAvailable: jest.fn(),
|
||||
};
|
||||
|
||||
describe('RulesCard', () => {
|
||||
describe('DashboardsCard', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
@ -34,7 +35,8 @@ describe('RulesCard', () => {
|
|||
expect(getByTestId('dashboardsDescription')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('card callout should be rendered if integrations cards is not complete', () => {
|
||||
it('card callout should be rendered if integrations card is available but not complete', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(true);
|
||||
props.isCardComplete.mockReturnValueOnce(false);
|
||||
|
||||
const { getByText } = render(
|
||||
|
@ -46,7 +48,20 @@ describe('RulesCard', () => {
|
|||
expect(getByText('To view dashboards add integrations first.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('card button should be disabled if integrations cards is not complete', () => {
|
||||
it('card callout should not be rendered if integrations card is not available', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(false);
|
||||
|
||||
const { queryByText } = render(
|
||||
<TestProviders>
|
||||
<DashboardsCard {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(queryByText('To view dashboards add integrations first.')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('card button should be disabled if integrations card is available but not complete', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(true);
|
||||
props.isCardComplete.mockReturnValueOnce(false);
|
||||
|
||||
const { getByTestId } = render(
|
||||
|
@ -57,7 +72,22 @@ describe('RulesCard', () => {
|
|||
|
||||
expect(getByTestId('dashboardsCardButton').querySelector('button')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('card button should be enabled if integrations card is complete', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(true);
|
||||
props.isCardComplete.mockReturnValueOnce(true);
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<DashboardsCard {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(getByTestId('dashboardsCardButton').querySelector('button')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('should expand integrations card when callout link is clicked', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(true);
|
||||
props.isCardComplete.mockReturnValueOnce(false); // To show the callout
|
||||
|
||||
const { getByTestId } = render(
|
||||
|
|
|
@ -20,12 +20,18 @@ export const DashboardsCard: OnboardingCardComponent = ({
|
|||
isCardComplete,
|
||||
setComplete,
|
||||
setExpandedCardId,
|
||||
isCardAvailable,
|
||||
}) => {
|
||||
const isIntegrationsCardComplete = useMemo(
|
||||
() => isCardComplete(OnboardingCardId.integrations),
|
||||
[isCardComplete]
|
||||
);
|
||||
|
||||
const isIntegrationsCardAvailable = useMemo(
|
||||
() => isCardAvailable(OnboardingCardId.integrations),
|
||||
[isCardAvailable]
|
||||
);
|
||||
|
||||
const expandIntegrationsCard = useCallback(() => {
|
||||
setExpandedCardId(OnboardingCardId.integrations, { scroll: true });
|
||||
}, [setExpandedCardId]);
|
||||
|
@ -45,7 +51,7 @@ export const DashboardsCard: OnboardingCardComponent = ({
|
|||
<EuiText data-test-subj="dashboardsDescription" size="s" color="subdued">
|
||||
{i18n.DASHBOARDS_CARD_DESCRIPTION}
|
||||
</EuiText>
|
||||
{!isIntegrationsCardComplete && (
|
||||
{isIntegrationsCardAvailable && !isIntegrationsCardComplete && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<CardCallOut
|
||||
|
@ -76,7 +82,7 @@ export const DashboardsCard: OnboardingCardComponent = ({
|
|||
cardId={OnboardingCardId.dashboards}
|
||||
deepLinkId={SecurityPageName.dashboards}
|
||||
fill
|
||||
isDisabled={!isIntegrationsCardComplete}
|
||||
isDisabled={isIntegrationsCardAvailable && !isIntegrationsCardComplete}
|
||||
>
|
||||
{i18n.DASHBOARDS_CARD_GO_TO_DASHBOARDS_BUTTON}
|
||||
</CardLinkButton>
|
||||
|
|
|
@ -15,6 +15,7 @@ const props = {
|
|||
checkComplete: jest.fn(),
|
||||
isCardComplete: jest.fn(),
|
||||
setExpandedCardId: jest.fn(),
|
||||
isCardAvailable: jest.fn(),
|
||||
};
|
||||
|
||||
describe('IntegrationsCard', () => {
|
||||
|
|
|
@ -13,6 +13,7 @@ const props = {
|
|||
setComplete: jest.fn(),
|
||||
checkComplete: jest.fn(),
|
||||
isCardComplete: jest.fn(),
|
||||
isCardAvailable: jest.fn(),
|
||||
setExpandedCardId: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -31,7 +32,8 @@ describe('RulesCard', () => {
|
|||
expect(getByTestId('rulesCardDescription')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('card callout should be rendered if integrations cards is not complete', () => {
|
||||
it('card callout should be rendered if integrations card is available but not complete', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(true);
|
||||
props.isCardComplete.mockReturnValueOnce(false);
|
||||
|
||||
const { getByText } = render(
|
||||
|
@ -43,7 +45,20 @@ describe('RulesCard', () => {
|
|||
expect(getByText('To add Elastic rules add integrations first.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('card button should be disabled if integrations cards is not complete', () => {
|
||||
it('card callout should not be rendered if integrations card is not available', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(false);
|
||||
|
||||
const { queryByText } = render(
|
||||
<TestProviders>
|
||||
<RulesCard {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(queryByText('To add Elastic rules add integrations first.')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('card button should be disabled if integrations card is available but not complete', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(true);
|
||||
props.isCardComplete.mockReturnValueOnce(false);
|
||||
|
||||
const { getByTestId } = render(
|
||||
|
@ -54,4 +69,17 @@ describe('RulesCard', () => {
|
|||
|
||||
expect(getByTestId('rulesCardButton').querySelector('button')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('card button should be enabled if integrations card is complete', () => {
|
||||
props.isCardAvailable.mockReturnValueOnce(true);
|
||||
props.isCardComplete.mockReturnValueOnce(true);
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<RulesCard {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(getByTestId('rulesCardButton').querySelector('button')).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,12 +16,21 @@ import { CardCallOut } from '../common/card_callout';
|
|||
import rulesImageSrc from './images/rules.png';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export const RulesCard: OnboardingCardComponent = ({ isCardComplete, setExpandedCardId }) => {
|
||||
export const RulesCard: OnboardingCardComponent = ({
|
||||
isCardComplete,
|
||||
setExpandedCardId,
|
||||
isCardAvailable,
|
||||
}) => {
|
||||
const isIntegrationsCardComplete = useMemo(
|
||||
() => isCardComplete(OnboardingCardId.integrations),
|
||||
[isCardComplete]
|
||||
);
|
||||
|
||||
const isIntegrationsCardAvailable = useMemo(
|
||||
() => isCardAvailable(OnboardingCardId.integrations),
|
||||
[isCardAvailable]
|
||||
);
|
||||
|
||||
const expandIntegrationsCard = useCallback(() => {
|
||||
setExpandedCardId(OnboardingCardId.integrations, { scroll: true });
|
||||
}, [setExpandedCardId]);
|
||||
|
@ -38,7 +47,7 @@ export const RulesCard: OnboardingCardComponent = ({ isCardComplete, setExpanded
|
|||
<EuiText data-test-subj="rulesCardDescription" size="s" color="subdued">
|
||||
{i18n.RULES_CARD_DESCRIPTION}
|
||||
</EuiText>
|
||||
{!isIntegrationsCardComplete && (
|
||||
{isIntegrationsCardAvailable && !isIntegrationsCardComplete && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<CardCallOut
|
||||
|
@ -63,7 +72,7 @@ export const RulesCard: OnboardingCardComponent = ({ isCardComplete, setExpanded
|
|||
<SecuritySolutionLinkButton
|
||||
deepLinkId={SecurityPageName.rules}
|
||||
fill
|
||||
isDisabled={!isIntegrationsCardComplete}
|
||||
isDisabled={isIntegrationsCardAvailable && !isIntegrationsCardComplete}
|
||||
>
|
||||
{i18n.RULES_CARD_ADD_RULES_BUTTON}
|
||||
</SecuritySolutionLinkButton>
|
||||
|
|
|
@ -14,6 +14,7 @@ import { OnboardingCardGroup } from './onboarding_card_group';
|
|||
import { OnboardingCardPanel } from './onboarding_card_panel';
|
||||
import { useExpandedCard } from './hooks/use_expanded_card';
|
||||
import { useCompletedCards } from './hooks/use_completed_cards';
|
||||
import type { IsCardAvailable } from '../../types';
|
||||
|
||||
export const OnboardingBody = React.memo(() => {
|
||||
const bodyConfig = useBodyConfig();
|
||||
|
@ -48,6 +49,12 @@ export const OnboardingBody = React.memo(() => {
|
|||
[checkCardComplete]
|
||||
);
|
||||
|
||||
const isCardAvailable = useCallback<IsCardAvailable>(
|
||||
(cardId: OnboardingCardId) =>
|
||||
bodyConfig.some((group) => group.cards.some((card) => card.id === cardId)),
|
||||
[bodyConfig]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="xl">
|
||||
{bodyConfig.map((group, index) => (
|
||||
|
@ -73,6 +80,7 @@ export const OnboardingBody = React.memo(() => {
|
|||
setComplete={createSetCardComplete(id)}
|
||||
checkComplete={createCheckCardComplete(id)}
|
||||
isCardComplete={isCardComplete}
|
||||
isCardAvailable={isCardAvailable}
|
||||
setExpandedCardId={setExpandedCardId}
|
||||
checkCompleteMetadata={cardCheckCompleteResult?.metadata}
|
||||
/>
|
||||
|
|
|
@ -41,6 +41,7 @@ export type CheckCompleteResponse<TMetadata extends {} = {}> =
|
|||
|
||||
export type SetComplete = (isComplete: boolean) => void;
|
||||
export type IsCardComplete = (cardId: OnboardingCardId) => boolean;
|
||||
export type IsCardAvailable = (cardId: OnboardingCardId) => boolean;
|
||||
export type SetExpandedCardId = (
|
||||
cardId: OnboardingCardId | null,
|
||||
options?: { scroll?: boolean }
|
||||
|
@ -59,6 +60,10 @@ export type OnboardingCardComponent<TMetadata extends {} = {}> = React.Component
|
|||
* Function to check if a specific card is complete.
|
||||
*/
|
||||
isCardComplete: IsCardComplete;
|
||||
/**
|
||||
* Function to check if a specific card is rendered.
|
||||
*/
|
||||
isCardAvailable: IsCardAvailable;
|
||||
/**
|
||||
* Function to expand a specific card ID and scroll to it.
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue