How to move breadcrumbs of plugin to top header

Hi Team,

I am trying to create a plugin in kibana. I need to move my breadcrumbs to the top nav bar where kibana breadcrumbs are being displayed.

I have attached screenshot explaining the same. Please help me out in this.

On the core services passed into your plugin there is core.chrome.setBreadcrumbs.

This is a real-world example about how to use it: https://github.com/elastic/kibana/blob/master/x-pack/plugins/lens/public/app_plugin/app.tsx#L233

Hi Joe,
I have used core.chrome.setBreadcrumbs as suggested by you as shown below

But I am getting "Cannot read property 'chrome' of undefined" at highlighted line.

Are you sure you are passing in the right "core"? Can you show the code registering the app and rendering ´CorreltionAppApp´?

Hi Joe,

This is mine code registering the app.

Earlier i had missed to import core there. But after importing core I am getting different error.
"Cannot destructure property 'core' of 'undefined' as it is undefined."

You are probably not calling renderApp with the right parameters, please check the whole stack from the setup method of your plugin through core.application.register to your actual app component whether the parameters you are passing down have the expected values

Hi Joe,

This is mine plugin registration code.

I have passed AppMountContext['core'] as specified above and tried to print core in app.tsx

Can you please help me out in this why is this so?

The second parameter of setup is not AppMountContext['core'] - depending on which version you are targeting it's deprecated and shouldn't be used anymore - just remove the second the parameter from setup and use coreStart.chrome instead, this is the right reference.

In your renderApp function, jsut pass the first CoreStart parameter down completely to your app and use it there.

Hi Joe,

Thanks for your help. Breadcrumbs are coming on top now, actuall issue was that i was importing CoreStart from different location on changing it to kibana/public it worked.

But now I am facing another issue. Breadcrumbs are not getting updated using core.chrome.setBreadcrumbs(). Below is mine code. I think state of the breadcrumbs are not getting changed.

import { Route, Switch, NavLink, useHistory } from 'react-router-dom';
import {
  EuiHeader,
  EuiFieldSearch,
  EuiHeaderLogo,
  EuiPageBody,
  EuiPageHeader,
  EuiPageHeaderSection,
  EuiPageContent,
  EuiPageContentBody,
  EuiBreadcrumbs,
  EuiFlexGroup,
  EuiFlexItem,
  EuiToolTip,
  EuiIcon,
  EuiDatePickerRange,
  EuiDatePicker,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
import { BrowserRouter as Router } from 'react-router-dom';

import {
  EuiTitle,
  EuiText,
  EuiTabbedContent,
  EuiSpacer,
  EuiButton,
  EuiPanel,
  EuiSearchBar,
} from '@elastic/eui';

import { CoreStart } from '../../../../src/core/public';
import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
import { AppMountContext, AppMountParameters, NotificationsStart } from 'kibana/public';

import { PLUGIN_ID, PLUGIN_NAME } from '../../common';
import AlaramList from './alarms-list';
import moment from 'moment';
import OverView from "./Overview/overview";
import AlertList from './alert-list';
import CorrelationRule from './correlation-rule/correlation-rule';
import CorrelationRuleList from './correlation-rule/correlation-rule-list';
import NewCorrelationRule from './correlation-rule/new-correlation';
import EditCorrelationRule from './correlation-rule/edit-correlation';
import AdminScreen from './Admin_Screen/adminScreenComponent';
import AddGroupName from './Admin_Screen/addGroupName';
import Alarm from './alarms/alarms';
import CaseManagement from './case-management/case-management';


interface CorrelationAppAppDeps {
  basename: string;
  notifications: CoreStart['notifications'];
  http: CoreStart['http'];
  navigation: NavigationPublicPluginStart;
  core: AppMountContext['core'];
}

export const CorrelationAppApp = ({
  basename,
  notifications,
  http,
  navigation,
  core
}: CorrelationAppAppDeps) => {

  let history = useHistory();

  const [position, setPosition] = useState('static');
  const [correlationData, setCorrelationData] = useState({});
  const [isRuleEditable, setRuleEditable] = useState(false);

  const initialQuery = EuiSearchBar.Query.MATCH_ALL;
  const [query, setQuery] = useState(initialQuery);
  const [error, setError] = useState(null);

  const [startDate, setStartDate] = useState(moment().subtract(6, "d"));
  const [endDate, setEndDate] = useState(moment());

  const [propStartDate, setPropStartDate] = useState(moment().subtract(6, "d"));
  const [propEndDate, setPropEndDate] = useState(moment());
  const [propQuery, setPropQuery] = useState(initialQuery);

  const [breadCrumbs, setBreadCrumbs] = useState([
    {
      text: 'Alert List',
      href: '#',
      onClick: e => {
        e.preventDefault();
        correlationClick("Alert List");
        history.replace("/app/correlationapp/correlationrule");
      }
    },
  ]);

  const onChange = ({ query, error }) => {
    if (error) {
      setError(error);
    } else {
      setError(null);
      setQuery(query);
    }
  };

  const handleChangeStart = (date) => {
    setStartDate(date);
  }

  const handleChangeEnd = (date) => {
    setEndDate(date);
  }

  const onRefreshClick = () => {
    console.log('path - ', history.location.pathname);
    console.log('query', query)
    const searchQuery = EuiSearchBar.Query.toESQuery(query);
    setPropQuery(searchQuery);
    setPropStartDate(startDate);
    setPropEndDate(endDate);
  }

  const renderSearch = () => {

    const schema = {
      strict: true,
      fields: {
        "_id": {
          type: 'string'
        },
        "@timestamp": {
          type: 'string'
        },
        "src.user": {
          type: 'string'
        },
        "event.name": {
          type: 'string'
        },
        "event.count": {
          type: 'string'
        },
      },
    };
    return (
      <EuiSearchBar className="fl ft p10 mt10 ml10"
        defaultQuery={initialQuery}
        box={{
          placeholder: 'e.g: type the Group Name',
          incremental: true,
          schema,
        }}
        onChange={onChange}
      />
    );
  }

  const searchBar = (
    <EuiFlexGroup className="ml10 mr10">
      <EuiFlexItem>
        {renderSearch()}
      </EuiFlexItem>
      <EuiFlexItem grow={false}>
        <EuiToolTip
          position="left"
          title="Search query syntax"
          content="For 'OR' condition the query should be enclosed in round brackets eg.(_id:'v91gbnQBNaptJ3V4XcXG' or event.count>2000). For 'AND' seperate both condition by space and don't use 'and' eg. _id:'v91gbnQBNaptJ3V4XcXG' event.count>2000">
          <EuiIcon type="questionInCircle" />
        </EuiToolTip>
      </EuiFlexItem>
      <EuiFlexItem className="datePckrMaxWidth">
        <EuiDatePickerRange
          startDateControl={
            <EuiDatePicker
              selected={startDate}
              onChange={handleChangeStart}
              startDate={startDate}
              endDate={endDate}
              isInvalid={startDate > endDate}
              aria-label="Start date"
              showTimeSelect
            />
          }
          endDateControl={
            <EuiDatePicker
              selected={endDate}
              onChange={handleChangeEnd}
              startDate={startDate}
              endDate={endDate}
              isInvalid={startDate > endDate}
              aria-label="End date"
              showTimeSelect
            />
          }
        />
      </EuiFlexItem>
      <EuiFlexItem className="refreshMaxWidth">
        <EuiButton fill color="primary" onClick={onRefreshClick}>
          <EuiIcon type="refresh" /> Refresh
						</EuiButton>
      </EuiFlexItem>
    </EuiFlexGroup>
  )

  const tabs = (
    <EuiFlexGroup>
      <EuiFlexItem>
        <ul className="NavigationItems">
          <li className="NavigationItem">
            <NavLink
              to="/app/correlationapp/correlationrule"
              activeClassName="active">Correlation</NavLink>
          </li>
          <div className="tabVerticalSeperator"></div>
          <li className="NavigationItem">
            <NavLink
              to="/app/correlationapp/adminscreen/groups"
              activeClassName="active">Administration</NavLink>
          </li>
        </ul>
      </EuiFlexItem>
    </EuiFlexGroup>
  )

  const sections = [
    {
      items: [<EuiHeaderLogo iconType="logoKibana"></EuiHeaderLogo>, tabs]
    },
    {
      items: [searchBar]
    },
  ];

  // Sync Kibana breadcrumbss
  useEffect(() => {
    console.log('useEffect brdcrmbs', breadCrumbs);
    core.chrome.setBreadcrumbs(breadCrumbs);
  }, [
    core.application,
    core.chrome,
    core.http.basePath
  ]);

  const mngBrdCrmbs = (val, data) => {
    if (history.location.pathname.includes('correlationrule')) {
      manageCorrelationRuleBrdcrmbs(val, data);
    }
  }

  const correlationClick = (clickedOn) => {
    let tempbrdcrmps = [];
    switch (clickedOn) {
      case "Correlation List":
        tempbrdcrmps = breadCrumbs.filter(item => item.text === "Alert List" || item.text === "Correlation List");
        setBreadCrumbs(tempbrdcrmps);
        history.replace("/app/correlationapp/correlationrule/rules");
        break;
      case "Alert List":
        tempbrdcrmps = breadCrumbs.filter(item => item.text === "Alert List");
        setBreadCrumbs(tempbrdcrmps);
        history.replace("/app/correlationapp/correlationrule/alerts");
        break;

      default:
        break;
    }
    core.chrome.setBreadcrumbs(breadCrumbs);
  }

  const manageCorrelationRuleBrdcrmbs = (val, data) => {
    let tempbrdcrmps = [];
    switch (val) {
      case "Correlation List":
        if (breadCrumbs.some(item => item.text === "Correlation List")) {
          correlationClick("Correlation List");
        } else {
          tempbrdcrmps = breadCrumbs;
          tempbrdcrmps.push({
            text: val,
            href: '#',
            onClick: e => {
              e.preventDefault();
              correlationClick(val);
            }
          });
          history.replace("/app/correlationapp/correlationrule/rules");
          setBreadCrumbs(tempbrdcrmps);
        }
        break;
      case "New Rule":
        tempbrdcrmps = breadCrumbs;
        tempbrdcrmps.push({
          text: "New Rule",
          href: '#',
          onClick: e => {
            e.preventDefault();
          }
        });
        history.replace("/app/correlationapp/correlationrule/newrule");
        setBreadCrumbs(tempbrdcrmps);
        break;

      default:
        setCorrelationData(data);
        http.get('/users/').then((res) => {
          const data = res.Response;
          console.log("groups", data.groups__name);
          setRuleEditable(data.groups__name.includes("KibanaAdmin"));
          history.replace("/app/correlationapp/correlationrule/rule/" + val);
        });
        tempbrdcrmps = breadCrumbs;
        tempbrdcrmps.push({
          text: data._source.name,
          href: '#',
          onClick: e => {
            e.preventDefault();
          }
        });
        setBreadCrumbs(tempbrdcrmps);
        break;
    }
    console.log('manageCorrelationRuleBrdcrmbs', breadCrumbs);
    core.chrome.setBreadcrumbs(breadCrumbs);
    console.log('breadcrumbs', core.chrome.getBreadcrumbs$())
  }

  return (
    <div className="correlationApp">
      <EuiHeader position='static' sections={sections} />

      <Switch>
        <Route path="/app/correlationapp/adminscreen/groups" exact
          render={(props) => (<AdminScreen http={http} />)} />
        <Route path="/app/correlationapp/adminscreen/groups/addgroup" exact
          render={(props) => (<AddGroupName http={http} />)} />
        <Route path="/app/correlationapp/correlationrule" exact
          render={(props) => (<CorrelationRule http={http} mngBrdCrmbs={mngBrdCrmbs} query={propQuery} startDate={propStartDate} endDate={propEndDate} />)} />
        <Route path="/app/correlationapp/correlationrule/alerts" exact
          render={(props) => (<AlertList http={http} mngBrdCrmbs={mngBrdCrmbs} />)} />
        <Route path="/app/correlationapp/correlationrule/rules" exact
          render={(props) => (<CorrelationRuleList http={http} mngBrdCrmbs={mngBrdCrmbs} />)} />
        <Route path="/app/correlationapp/correlationrule/rule/"
          render={(props) => (<EditCorrelationRule http={http} isEditable={isRuleEditable} ruleData={correlationData} mngBrdCrmbs={mngBrdCrmbs} />)} />
        <Route path="/app/correlationapp/correlationrule/newrule" exact
          render={(props) => (<NewCorrelationRule http={http} mngBrdCrmbs={mngBrdCrmbs} />)} />
        <Route path="/app/correlationapp/" exact component={OverView} />
        <Route path="/app/correlationapp/casemanagement" exact
          render={(props) => (<CaseManagement http={http} />)} />
      </Switch>
    </div>
  );
};

Below is the screen shot of console log. where you "manageCorrelationRuleBrdcrmbs" is the breadcrumb I am passing and after that is core.chrome.getBreadcrumbs(). In that also in value you can see breadcrumbs are added but its not reflected on top.

Can you please help me out in this.

Regards
Praveer.

This component seems a little messy, it's hard to tell what's going on. It seems like there is an effect hook syncing a local breadcrumb variable with the core chrome state, but it's not triggered when the local state changes?

Going forward I would recommend starting with a minimal example - comment out everything besides a button which, when clicked, sets the breadcrumb. If you got that working, start re-enabling things and make sure everything keeps working.

My guess is that something else in your code is immediately resetting the breadcrumbs after manageCorrelationRuleBrdcrmbs but it's hard to tell without being able to run the code.

Hi Joe,

Thanks for your help. Now bread crumbs are appearing properly on top header pane.
While binding array in core.chrome.setBreadcrumbs() that was getting changed because while manipulating the array passed it was getting changed as it wasn't cloned with another array. On cloning array it worked.

Thanks for your help.

Regards
Praveer.

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.