mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[8.18] [Security Solution] [AI Assistant] security assistant content references tour (#208775) (#209474)
# Backport This will backport the following commits from `main` to `8.18`: - [[Security Solution] [AI Assistant] security assistant content references tour (#208775)](https://github.com/elastic/kibana/pull/208775) <!--- Backport version: 9.6.4 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Kenneth Kreindler","email":"42113355+KDKHD@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-02-03T22:47:41Z","message":"[Security Solution] [AI Assistant] security assistant content references tour (#208775)\n\n## Summary\r\nFollow up to : https://github.com/elastic/kibana/pull/206683\r\n\r\nThis PR adds a tour that tells the user how to toggle citations on and\r\noff and how to show and hide anonymized values.\r\n\r\n### How to test:\r\n- Enable feature flag: \r\n```yaml\r\n# kibana.dev.yml\r\nxpack.securitySolution.enableExperimental: ['contentReferencesEnabled']\r\n```\r\n- Launch the security AI assistant\r\n- Now we need to get the assistant to reply with a message that contains\r\neither anonymized values or citations. This is what triggers the tour.\r\nTo do this ask it a question about one of your KB documents or an alert\r\nthat contains anonymized properties or returns a citation.\r\n- Once the assistant stream ends, the tour should appear 1 second later\r\n(unless the knowledge base tour is open).\r\n\r\nThe tour will only appear one time per browser. To make it appear again,\r\nclear the key\r\n`elasticAssistant.anonymizedValuesAndCitationsTourCompleted` from local\r\nstorage.\r\n\r\nAlso fixes a\r\n[typo](97fca992
-d39d-43e7-8e73-a11daf7549ca\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- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n- [x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [x] [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\r\n- [x] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [x] This was checked for breaking HTTP API changes, and any breaking\r\nchanges have been approved by the breaking-change committee. The\r\n`release_note:breaking` label should be applied in these situations.\r\n- [x] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [x] The PR description includes the appropriate Release Notes section,\r\nand the correct `release_note:*` label is applied per the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n### Identify risks\r\n\r\nDoes this PR introduce any risks? For example, consider risks like hard\r\nto test bugs, performance regression, potential of data loss.\r\n\r\nDescribe the risk, its severity, and mitigation for each identified\r\nrisk. Invite stakeholders and evaluate how to proceed before merging.\r\n\r\n- [ ] [See some risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\r\n- [ ] ...\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\r\nCo-authored-by: Steph Milovic <stephanie.milovic@elastic.co>","sha":"572e6656d1dcd0e5a54b026fcda9d0a277c8357f","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Security Generative AI","backport:version","v8.18.0","v9.1.0"],"title":"[Security Solution] [AI Assistant] security assistant content references tour","number":208775,"url":"https://github.com/elastic/kibana/pull/208775","mergeCommit":{"message":"[Security Solution] [AI Assistant] security assistant content references tour (#208775)\n\n## Summary\r\nFollow up to : https://github.com/elastic/kibana/pull/206683\r\n\r\nThis PR adds a tour that tells the user how to toggle citations on and\r\noff and how to show and hide anonymized values.\r\n\r\n### How to test:\r\n- Enable feature flag: \r\n```yaml\r\n# kibana.dev.yml\r\nxpack.securitySolution.enableExperimental: ['contentReferencesEnabled']\r\n```\r\n- Launch the security AI assistant\r\n- Now we need to get the assistant to reply with a message that contains\r\neither anonymized values or citations. This is what triggers the tour.\r\nTo do this ask it a question about one of your KB documents or an alert\r\nthat contains anonymized properties or returns a citation.\r\n- Once the assistant stream ends, the tour should appear 1 second later\r\n(unless the knowledge base tour is open).\r\n\r\nThe tour will only appear one time per browser. To make it appear again,\r\nclear the key\r\n`elasticAssistant.anonymizedValuesAndCitationsTourCompleted` from local\r\nstorage.\r\n\r\nAlso fixes a\r\n[typo](97fca992
-d39d-43e7-8e73-a11daf7549ca\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- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n- [x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [x] [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\r\n- [x] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [x] This was checked for breaking HTTP API changes, and any breaking\r\nchanges have been approved by the breaking-change committee. The\r\n`release_note:breaking` label should be applied in these situations.\r\n- [x] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [x] The PR description includes the appropriate Release Notes section,\r\nand the correct `release_note:*` label is applied per the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n### Identify risks\r\n\r\nDoes this PR introduce any risks? For example, consider risks like hard\r\nto test bugs, performance regression, potential of data loss.\r\n\r\nDescribe the risk, its severity, and mitigation for each identified\r\nrisk. Invite stakeholders and evaluate how to proceed before merging.\r\n\r\n- [ ] [See some risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\r\n- [ ] ...\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\r\nCo-authored-by: Steph Milovic <stephanie.milovic@elastic.co>","sha":"572e6656d1dcd0e5a54b026fcda9d0a277c8357f"}},"sourceBranch":"main","suggestedTargetBranches":["8.18"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/209425","number":209425,"state":"MERGED","mergeCommit":{"sha":"c7ee7830e360192d20f16e192d238bc1a28fe9ab","message":"[9.0] [Security Solution] [AI Assistant] security assistant content references tour (#208775) (#209425)\n\n# Backport\n\nThis will backport the following commits from `main` to `9.0`:\n- [[Security Solution] [AI Assistant] security assistant content\nreferences tour\n(#208775)](https://github.com/elastic/kibana/pull/208775)\n\n<!--- Backport version: 9.4.3 -->\n\n### Questions ?\nPlease refer to the [Backport tool\ndocumentation](https://github.com/sqren/backport)\n\n<!--BACKPORT [{\"author\":{\"name\":\"Kenneth\nKreindler\",\"email\":\"42113355+KDKHD@users.noreply.github.com\"},\"sourceCommit\":{\"committedDate\":\"2025-02-03T22:47:41Z\",\"message\":\"[Security\nSolution] [AI Assistant] security assistant content references tour\n(#208775)\\n\\n## Summary\\r\\nFollow up to :\nhttps://github.com/elastic/kibana/pull/206683\\r\\n\\r\\nThis PR adds a tour\nthat tells the user how to toggle citations on and\\r\\noff and how to\nshow and hide anonymized values.\\r\\n\\r\\n### How to test:\\r\\n- Enable\nfeature flag: \\r\\n```yaml\\r\\n#\nkibana.dev.yml\\r\\nxpack.securitySolution.enableExperimental:\n['contentReferencesEnabled']\\r\\n```\\r\\n- Launch the security AI\nassistant\\r\\n- Now we need to get the assistant to reply with a message\nthat contains\\r\\neither anonymized values or citations. This is what\ntriggers the tour.\\r\\nTo do this ask it a question about one of your KB\ndocuments or an alert\\r\\nthat contains anonymized properties or returns\na citation.\\r\\n- Once the assistant stream ends, the tour should appear\n1 second later\\r\\n(unless the knowledge base tour is open).\\r\\n\\r\\nThe\ntour will only appear one time per browser. To make it appear\nagain,\\r\\nclear the\nkey\\r\\n`elasticAssistant.anonymizedValuesAndCitationsTourCompleted` from\nlocal\\r\\nstorage.\\r\\n\\r\\nAlso fixes\na\\r\\n[typo](97fca992
-d39d-43e7-8e73-a11daf7549ca\\r\\n\\r\\n\\r\\n###\nChecklist\\r\\n\\r\\nCheck the PR satisfies following conditions.\n\\r\\n\\r\\nReviewers should verify this PR satisfies this list as\nwell.\\r\\n\\r\\n- [x] Any text added follows [EUI's\nwriting\\r\\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),\nuses\\r\\nsentence case text and includes\n[i18n\\r\\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\\r\\n-\n[x]\\r\\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\\r\\nwas\nadded for features that require explanation or tutorials\\r\\n- [x] [Unit\nor\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated or added to match the most common scenarios\\r\\n- [x] If a plugin\nconfiguration key changed, check if it needs to be\\r\\nallowlisted in the\ncloud and added to the\n[docker\\r\\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\\r\\n-\n[x] This was checked for breaking HTTP API changes, and any\nbreaking\\r\\nchanges have been approved by the breaking-change committee.\nThe\\r\\n`release_note:breaking` label should be applied in these\nsituations.\\r\\n- [x] [Flaky\nTest\\r\\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)\nwas\\r\\nused on any tests changed\\r\\n- [x] The PR description includes\nthe appropriate Release Notes section,\\r\\nand the correct\n`release_note:*` label is applied per\nthe\\r\\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\\r\\n\\r\\n###\nIdentify risks\\r\\n\\r\\nDoes this PR introduce any risks? For example,\nconsider risks like hard\\r\\nto test bugs, performance regression,\npotential of data loss.\\r\\n\\r\\nDescribe the risk, its severity, and\nmitigation for each identified\\r\\nrisk. Invite stakeholders and evaluate\nhow to proceed before merging.\\r\\n\\r\\n- [ ] [See some\nrisk\\r\\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\\r\\n-\n[ ] ...\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by: kibanamachine\n<42973632+kibanamachine@users.noreply.github.com>\\r\\nCo-authored-by:\nElastic Machine\n<elasticmachine@users.noreply.github.com>\\r\\nCo-authored-by: Steph\nMilovic\n<stephanie.milovic@elastic.co>\",\"sha\":\"572e6656d1dcd0e5a54b026fcda9d0a277c8357f\",\"branchLabelMapping\":{\"^v9.1.0$\":\"main\",\"^v8.19.0$\":\"8.x\",\"^v(\\\\d+).(\\\\d+).\\\\d+$\":\"$1.$2\"}},\"sourcePullRequest\":{\"labels\":[\"release_note:skip\",\"v9.0.0\",\"Team:Security\nGenerative AI\",\"backport:version\",\"v8.18.0\",\"v9.1.0\"],\"title\":\"[Security\nSolution] [AI Assistant] security assistant content references\ntour\",\"number\":208775,\"url\":\"https://github.com/elastic/kibana/pull/208775\",\"mergeCommit\":{\"message\":\"[Security\nSolution] [AI Assistant] security assistant content references tour\n(#208775)\\n\\n## Summary\\r\\nFollow up to :\nhttps://github.com/elastic/kibana/pull/206683\\r\\n\\r\\nThis PR adds a tour\nthat tells the user how to toggle citations on and\\r\\noff and how to\nshow and hide anonymized values.\\r\\n\\r\\n### How to test:\\r\\n- Enable\nfeature flag: \\r\\n```yaml\\r\\n#\nkibana.dev.yml\\r\\nxpack.securitySolution.enableExperimental:\n['contentReferencesEnabled']\\r\\n```\\r\\n- Launch the security AI\nassistant\\r\\n- Now we need to get the assistant to reply with a message\nthat contains\\r\\neither anonymized values or citations. This is what\ntriggers the tour.\\r\\nTo do this ask it a question about one of your KB\ndocuments or an alert\\r\\nthat contains anonymized properties or returns\na citation.\\r\\n- Once the assistant stream ends, the tour should appear\n1 second later\\r\\n(unless the knowledge base tour is open).\\r\\n\\r\\nThe\ntour will only appear one time per browser. To make it appear\nagain,\\r\\nclear the\nkey\\r\\n`elasticAssistant.anonymizedValuesAndCitationsTourCompleted` from\nlocal\\r\\nstorage.\\r\\n\\r\\nAlso fixes\na\\r\\n[typo](97fca992
-d39d-43e7-8e73-a11daf7549ca\\r\\n\\r\\n\\r\\n###\nChecklist\\r\\n\\r\\nCheck the PR satisfies following conditions.\n\\r\\n\\r\\nReviewers should verify this PR satisfies this list as\nwell.\\r\\n\\r\\n- [x] Any text added follows [EUI's\nwriting\\r\\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),\nuses\\r\\nsentence case text and includes\n[i18n\\r\\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\\r\\n-\n[x]\\r\\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\\r\\nwas\nadded for features that require explanation or tutorials\\r\\n- [x] [Unit\nor\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated or added to match the most common scenarios\\r\\n- [x] If a plugin\nconfiguration key changed, check if it needs to be\\r\\nallowlisted in the\ncloud and added to the\n[docker\\r\\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\\r\\n-\n[x] This was checked for breaking HTTP API changes, and any\nbreaking\\r\\nchanges have been approved by the breaking-change committee.\nThe\\r\\n`release_note:breaking` label should be applied in these\nsituations.\\r\\n- [x] [Flaky\nTest\\r\\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)\nwas\\r\\nused on any tests changed\\r\\n- [x] The PR description includes\nthe appropriate Release Notes section,\\r\\nand the correct\n`release_note:*` label is applied per\nthe\\r\\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\\r\\n\\r\\n###\nIdentify risks\\r\\n\\r\\nDoes this PR introduce any risks? For example,\nconsider risks like hard\\r\\nto test bugs, performance regression,\npotential of data loss.\\r\\n\\r\\nDescribe the risk, its severity, and\nmitigation for each identified\\r\\nrisk. Invite stakeholders and evaluate\nhow to proceed before merging.\\r\\n\\r\\n- [ ] [See some\nrisk\\r\\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\\r\\n-\n[ ] ...\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by: kibanamachine\n<42973632+kibanamachine@users.noreply.github.com>\\r\\nCo-authored-by:\nElastic Machine\n<elasticmachine@users.noreply.github.com>\\r\\nCo-authored-by: Steph\nMilovic\n<stephanie.milovic@elastic.co>\",\"sha\":\"572e6656d1dcd0e5a54b026fcda9d0a277c8357f\"}},\"sourceBranch\":\"main\",\"suggestedTargetBranches\":[\"9.0\",\"8.18\"],\"targetPullRequestStates\":[{\"branch\":\"9.0\",\"label\":\"v9.0.0\",\"branchLabelMappingKey\":\"^v(\\\\d+).(\\\\d+).\\\\d+$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"},{\"branch\":\"8.18\",\"label\":\"v8.18.0\",\"branchLabelMappingKey\":\"^v(\\\\d+).(\\\\d+).\\\\d+$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"},{\"branch\":\"main\",\"label\":\"v9.1.0\",\"branchLabelMappingKey\":\"^v9.1.0$\",\"isSourceBranch\":true,\"state\":\"MERGED\",\"url\":\"https://github.com/elastic/kibana/pull/208775\",\"number\":208775,\"mergeCommit\":{\"message\":\"[Security\nSolution] [AI Assistant] security assistant content references tour\n(#208775)\\n\\n## Summary\\r\\nFollow up to :\nhttps://github.com/elastic/kibana/pull/206683\\r\\n\\r\\nThis PR adds a tour\nthat tells the user how to toggle citations on and\\r\\noff and how to\nshow and hide anonymized values.\\r\\n\\r\\n### How to test:\\r\\n- Enable\nfeature flag: \\r\\n```yaml\\r\\n#\nkibana.dev.yml\\r\\nxpack.securitySolution.enableExperimental:\n['contentReferencesEnabled']\\r\\n```\\r\\n- Launch the security AI\nassistant\\r\\n- Now we need to get the assistant to reply with a message\nthat contains\\r\\neither anonymized values or citations. This is what\ntriggers the tour.\\r\\nTo do this ask it a question about one of your KB\ndocuments or an alert\\r\\nthat contains anonymized properties or returns\na citation.\\r\\n- Once the assistant stream ends, the tour should appear\n1 second later\\r\\n(unless the knowledge base tour is open).\\r\\n\\r\\nThe\ntour will only appear one time per browser. To make it appear\nagain,\\r\\nclear the\nkey\\r\\n`elasticAssistant.anonymizedValuesAndCitationsTourCompleted` from\nlocal\\r\\nstorage.\\r\\n\\r\\nAlso fixes\na\\r\\n[typo](97fca992
-d39d-43e7-8e73-a11daf7549ca\\r\\n\\r\\n\\r\\n###\nChecklist\\r\\n\\r\\nCheck the PR satisfies following conditions.\n\\r\\n\\r\\nReviewers should verify this PR satisfies this list as\nwell.\\r\\n\\r\\n- [x] Any text added follows [EUI's\nwriting\\r\\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),\nuses\\r\\nsentence case text and includes\n[i18n\\r\\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\\r\\n-\n[x]\\r\\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\\r\\nwas\nadded for features that require explanation or tutorials\\r\\n- [x] [Unit\nor\nfunctional\\r\\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\\r\\nwere\nupdated or added to match the most common scenarios\\r\\n- [x] If a plugin\nconfiguration key changed, check if it needs to be\\r\\nallowlisted in the\ncloud and added to the\n[docker\\r\\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\\r\\n-\n[x] This was checked for breaking HTTP API changes, and any\nbreaking\\r\\nchanges have been approved by the breaking-change committee.\nThe\\r\\n`release_note:breaking` label should be applied in these\nsituations.\\r\\n- [x] [Flaky\nTest\\r\\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)\nwas\\r\\nused on any tests changed\\r\\n- [x] The PR description includes\nthe appropriate Release Notes section,\\r\\nand the correct\n`release_note:*` label is applied per\nthe\\r\\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\\r\\n\\r\\n###\nIdentify risks\\r\\n\\r\\nDoes this PR introduce any risks? For example,\nconsider risks like hard\\r\\nto test bugs, performance regression,\npotential of data loss.\\r\\n\\r\\nDescribe the risk, its severity, and\nmitigation for each identified\\r\\nrisk. Invite stakeholders and evaluate\nhow to proceed before merging.\\r\\n\\r\\n- [ ] [See some\nrisk\\r\\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\\r\\n-\n[ ] ...\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by: kibanamachine\n<42973632+kibanamachine@users.noreply.github.com>\\r\\nCo-authored-by:\nElastic Machine\n<elasticmachine@users.noreply.github.com>\\r\\nCo-authored-by: Steph\nMilovic\n<stephanie.milovic@elastic.co>\",\"sha\":\"572e6656d1dcd0e5a54b026fcda9d0a277c8357f\"}}]}]\nBACKPORT-->\n\nCo-authored-by: Kenneth Kreindler <42113355+KDKHD@users.noreply.github.com>"}},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/208775","number":208775,"mergeCommit":{"message":"[Security Solution] [AI Assistant] security assistant content references tour (#208775)\n\n## Summary\r\nFollow up to : https://github.com/elastic/kibana/pull/206683\r\n\r\nThis PR adds a tour that tells the user how to toggle citations on and\r\noff and how to show and hide anonymized values.\r\n\r\n### How to test:\r\n- Enable feature flag: \r\n```yaml\r\n# kibana.dev.yml\r\nxpack.securitySolution.enableExperimental: ['contentReferencesEnabled']\r\n```\r\n- Launch the security AI assistant\r\n- Now we need to get the assistant to reply with a message that contains\r\neither anonymized values or citations. This is what triggers the tour.\r\nTo do this ask it a question about one of your KB documents or an alert\r\nthat contains anonymized properties or returns a citation.\r\n- Once the assistant stream ends, the tour should appear 1 second later\r\n(unless the knowledge base tour is open).\r\n\r\nThe tour will only appear one time per browser. To make it appear again,\r\nclear the key\r\n`elasticAssistant.anonymizedValuesAndCitationsTourCompleted` from local\r\nstorage.\r\n\r\nAlso fixes a\r\n[typo](97fca992
-d39d-43e7-8e73-a11daf7549ca\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- [x] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n- [x]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [x] [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\r\n- [x] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [x] This was checked for breaking HTTP API changes, and any breaking\r\nchanges have been approved by the breaking-change committee. The\r\n`release_note:breaking` label should be applied in these situations.\r\n- [x] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [x] The PR description includes the appropriate Release Notes section,\r\nand the correct `release_note:*` label is applied per the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n### Identify risks\r\n\r\nDoes this PR introduce any risks? For example, consider risks like hard\r\nto test bugs, performance regression, potential of data loss.\r\n\r\nDescribe the risk, its severity, and mitigation for each identified\r\nrisk. Invite stakeholders and evaluate how to proceed before merging.\r\n\r\n- [ ] [See some risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\r\n- [ ] ...\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\r\nCo-authored-by: Steph Milovic <stephanie.milovic@elastic.co>","sha":"572e6656d1dcd0e5a54b026fcda9d0a277c8357f"}}]}] BACKPORT--> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f25de199b7
commit
97cd9adc4a
10 changed files with 519 additions and 179 deletions
|
@ -44,6 +44,9 @@ interface OwnProps {
|
|||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
|
||||
export const AI_ASSISTANT_SETTINGS_MENU_CONTAINER_ID = 'aiAssistantSettingsMenuContainer';
|
||||
|
||||
/**
|
||||
* Renders the header of the Elastic AI Assistant.
|
||||
* Provide a user interface for selecting and managing conversations,
|
||||
|
@ -162,7 +165,7 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
onConnectorSelected={onConversationChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexItem id={AI_ASSISTANT_SETTINGS_MENU_CONTAINER_ID}>
|
||||
<SettingsContextMenu isDisabled={isDisabled} onChatCleared={onChatCleared} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -66,7 +66,7 @@ export const SHOW_REAL_VALUES = i18n.translate(
|
|||
export const ANONYMIZE_VALUES = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.anonymizeValues',
|
||||
{
|
||||
defaultMessage: 'Show anonymize values',
|
||||
defaultMessage: 'Show anonymized values',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -89,7 +89,7 @@ const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
|
|||
export const ANONYMIZE_VALUES_TOOLTIP = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.anonymizeValues.tooltip',
|
||||
{
|
||||
values: { keyboardShortcut: isMac ? '⌥ a' : 'Alt a' },
|
||||
values: { keyboardShortcut: isMac ? '⌥ + a' : 'Alt + a' },
|
||||
defaultMessage:
|
||||
'Toggle to reveal or hide field values in your chat stream. The data sent to the LLM is still anonymized based on settings in the Anonymization panel. Keyboard shortcut: {keyboardShortcut}',
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ export const ANONYMIZE_VALUES_TOOLTIP = i18n.translate(
|
|||
export const SHOW_CITATIONS_TOOLTIP = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.showCitationsLabel.tooltip',
|
||||
{
|
||||
values: { keyboardShortcut: isMac ? '⌥ c' : 'Alt c' },
|
||||
values: { keyboardShortcut: isMac ? '⌥ + c' : 'Alt + c' },
|
||||
defaultMessage: 'Keyboard shortcut: {keyboardShortcut}',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -50,6 +50,7 @@ import { ConnectorMissingCallout } from '../connectorland/connector_missing_call
|
|||
import { ConversationSidePanel } from './conversations/conversation_sidepanel';
|
||||
import { SelectedPromptContexts } from './prompt_editor/selected_prompt_contexts';
|
||||
import { AssistantHeader } from './assistant_header';
|
||||
import { AnonymizedValuesAndCitationsTour } from '../tour/anonymized_values_and_citations_tour';
|
||||
|
||||
export const CONVERSATION_SIDE_PANEL_WIDTH = 220;
|
||||
|
||||
|
@ -446,196 +447,201 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction={'row'} wrap={false} gutterSize="none">
|
||||
{chatHistoryVisible && (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
inline-size: ${CONVERSATION_SIDE_PANEL_WIDTH}px;
|
||||
border-right: 1px solid ${euiThemeVars.euiColorLightShade};
|
||||
`}
|
||||
>
|
||||
<ConversationSidePanel
|
||||
currentConversation={currentConversation}
|
||||
onConversationSelected={handleOnConversationSelected}
|
||||
conversations={conversations}
|
||||
onConversationDeleted={handleOnConversationDeleted}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
refetchCurrentUserConversations={refetchCurrentUserConversations}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<>
|
||||
{contentReferencesEnabled && (
|
||||
<AnonymizedValuesAndCitationsTour conversation={currentConversation} />
|
||||
)}
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<CommentContainer data-test-subj="assistantChat">
|
||||
<EuiFlexGroup
|
||||
<EuiFlexGroup direction={'row'} wrap={false} gutterSize="none">
|
||||
{chatHistoryVisible && (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
inline-size: ${CONVERSATION_SIDE_PANEL_WIDTH}px;
|
||||
border-right: 1px solid ${euiThemeVars.euiColorLightShade};
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem
|
||||
<ConversationSidePanel
|
||||
currentConversation={currentConversation}
|
||||
onConversationSelected={handleOnConversationSelected}
|
||||
conversations={conversations}
|
||||
onConversationDeleted={handleOnConversationDeleted}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
refetchCurrentUserConversations={refetchCurrentUserConversations}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<CommentContainer data-test-subj="assistantChat">
|
||||
<EuiFlexGroup
|
||||
css={css`
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
`}
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<AssistantHeader
|
||||
isLoading={isInitialLoad}
|
||||
selectedConversation={currentConversation}
|
||||
defaultConnector={defaultConnector}
|
||||
isDisabled={isDisabled || isLoadingChatSend}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
onCloseFlyout={onCloseFlyout}
|
||||
onChatCleared={handleOnChatCleared}
|
||||
chatHistoryVisible={chatHistoryVisible}
|
||||
setChatHistoryVisible={setChatHistoryVisible}
|
||||
onConversationSelected={handleOnConversationSelected}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={isFetchedCurrentUserConversations}
|
||||
refetchCurrentUserConversations={refetchCurrentUserConversations}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
|
||||
{/* Create portals for each EuiCodeBlock to add the `Investigate in Timeline` action */}
|
||||
{createCodeBlockPortals()}
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
min-height: 100px;
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
`}
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<AssistantHeader
|
||||
isLoading={isInitialLoad}
|
||||
selectedConversation={currentConversation}
|
||||
defaultConnector={defaultConnector}
|
||||
isDisabled={isDisabled || isLoadingChatSend}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
onCloseFlyout={onCloseFlyout}
|
||||
onChatCleared={handleOnChatCleared}
|
||||
chatHistoryVisible={chatHistoryVisible}
|
||||
setChatHistoryVisible={setChatHistoryVisible}
|
||||
onConversationSelected={handleOnConversationSelected}
|
||||
conversations={conversations}
|
||||
conversationsLoaded={isFetchedCurrentUserConversations}
|
||||
refetchCurrentUserConversations={refetchCurrentUserConversations}
|
||||
onConversationCreate={handleCreateConversation}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
refetchPrompts={refetchPrompts}
|
||||
/>
|
||||
|
||||
> div {
|
||||
{/* Create portals for each EuiCodeBlock to add the `Investigate in Timeline` action */}
|
||||
{createCodeBlockPortals()}
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody
|
||||
css={css`
|
||||
min-height: 100px;
|
||||
flex: 1;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
> .euiFlyoutBody__banner {
|
||||
overflow-x: unset;
|
||||
}
|
||||
|
||||
> .euiFlyoutBody__overflowContent {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
`}
|
||||
banner={
|
||||
!isDisabled &&
|
||||
showMissingConnectorCallout &&
|
||||
isFetchedConnectors && (
|
||||
<ConnectorMissingCallout
|
||||
isConnectorConfigured={(connectors?.length ?? 0) > 0}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<AssistantBody
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
comments={comments}
|
||||
currentConversation={currentConversation}
|
||||
currentSystemPromptId={currentSystemPrompt?.id}
|
||||
handleOnConversationSelected={handleOnConversationSelected}
|
||||
http={http}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
isLoading={isInitialLoad}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
isWelcomeSetup={isWelcomeSetup}
|
||||
setCurrentSystemPromptId={setCurrentSystemPromptId}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter
|
||||
css={css`
|
||||
background: none;
|
||||
border-top: 1px solid ${euiThemeVars.euiColorLightShade};
|
||||
overflow: hidden;
|
||||
max-height: 60%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
> .euiFlyoutBody__banner {
|
||||
overflow-x: unset;
|
||||
}
|
||||
|
||||
> .euiFlyoutBody__overflowContent {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
`}
|
||||
banner={
|
||||
!isDisabled &&
|
||||
showMissingConnectorCallout &&
|
||||
isFetchedConnectors && (
|
||||
<ConnectorMissingCallout
|
||||
isConnectorConfigured={(connectors?.length ?? 0) > 0}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<AssistantBody
|
||||
allSystemPrompts={allSystemPrompts}
|
||||
comments={comments}
|
||||
currentConversation={currentConversation}
|
||||
currentSystemPromptId={currentSystemPrompt?.id}
|
||||
handleOnConversationSelected={handleOnConversationSelected}
|
||||
http={http}
|
||||
isAssistantEnabled={isAssistantEnabled}
|
||||
isLoading={isInitialLoad}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
isWelcomeSetup={isWelcomeSetup}
|
||||
setCurrentSystemPromptId={setCurrentSystemPromptId}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter
|
||||
css={css`
|
||||
background: none;
|
||||
border-top: 1px solid ${euiThemeVars.euiColorLightShade};
|
||||
overflow: hidden;
|
||||
max-height: 60%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`}
|
||||
>
|
||||
<EuiPanel
|
||||
paddingSize="m"
|
||||
hasShadow={false}
|
||||
css={css`
|
||||
overflow: auto;
|
||||
`}
|
||||
>
|
||||
{!isDisabled &&
|
||||
Object.keys(promptContexts).length !== selectedPromptContextsCount && (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<>
|
||||
<ContextPills
|
||||
anonymizationFields={anonymizationFields}
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
/>
|
||||
{Object.keys(promptContexts).length > 0 && <EuiSpacer size={'s'} />}
|
||||
</>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
<EuiPanel
|
||||
paddingSize="m"
|
||||
hasShadow={false}
|
||||
css={css`
|
||||
overflow: auto;
|
||||
`}
|
||||
>
|
||||
{!isDisabled &&
|
||||
Object.keys(promptContexts).length !== selectedPromptContextsCount && (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<>
|
||||
<ContextPills
|
||||
anonymizationFields={anonymizationFields}
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
/>
|
||||
{Object.keys(promptContexts).length > 0 && <EuiSpacer size={'s'} />}
|
||||
</>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{Object.keys(selectedPromptContexts).length ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectedPromptContexts
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
currentReplacements={currentConversation?.replacements}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{Object.keys(selectedPromptContexts).length ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectedPromptContexts
|
||||
promptContexts={promptContexts}
|
||||
selectedPromptContexts={selectedPromptContexts}
|
||||
setSelectedPromptContexts={setSelectedPromptContexts}
|
||||
currentReplacements={currentConversation?.replacements}
|
||||
<ChatSend
|
||||
handleChatSend={handleChatSend}
|
||||
setUserPrompt={setUserPrompt}
|
||||
handleRegenerateResponse={handleRegenerateResponse}
|
||||
isDisabled={isSendingDisabled}
|
||||
isLoading={isLoadingChatSend}
|
||||
shouldRefocusPrompt={shouldRefocusPrompt}
|
||||
userPrompt={userPrompt}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<ChatSend
|
||||
handleChatSend={handleChatSend}
|
||||
setUserPrompt={setUserPrompt}
|
||||
handleRegenerateResponse={handleRegenerateResponse}
|
||||
isDisabled={isSendingDisabled}
|
||||
isLoading={isLoadingChatSend}
|
||||
shouldRefocusPrompt={shouldRefocusPrompt}
|
||||
userPrompt={userPrompt}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
|
||||
{!isDisabled && (
|
||||
<EuiPanel
|
||||
css={css`
|
||||
background: ${euiThemeVars.euiColorLightestShade};
|
||||
`}
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
borderRadius="none"
|
||||
>
|
||||
<QuickPrompts
|
||||
setInput={setUserPrompt}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
trackPrompt={trackPrompt}
|
||||
allPrompts={allPrompts}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</CommentContainer>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{!isDisabled && (
|
||||
<EuiPanel
|
||||
css={css`
|
||||
background: ${euiThemeVars.euiColorLightestShade};
|
||||
`}
|
||||
hasShadow={false}
|
||||
paddingSize="m"
|
||||
borderRadius="none"
|
||||
>
|
||||
<QuickPrompts
|
||||
setInput={setUserPrompt}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
trackPrompt={trackPrompt}
|
||||
allPrompts={allPrompts}
|
||||
/>
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</CommentContainer>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants';
|
||||
import { Conversation } from '../..';
|
||||
import { ClientMessage, Conversation } from '../..';
|
||||
|
||||
export const alertConvo: Conversation = {
|
||||
id: '',
|
||||
|
@ -33,6 +33,20 @@ export const alertConvo: Conversation = {
|
|||
},
|
||||
};
|
||||
|
||||
export const messageWithContentReferences: ClientMessage = {
|
||||
content: 'You have 1 alert.{reference(abcde)}',
|
||||
role: 'user',
|
||||
timestamp: '2023-03-19T18:59:18.174Z',
|
||||
metadata: {
|
||||
contentReferences: {
|
||||
abcde: {
|
||||
id: 'abcde',
|
||||
type: 'SecurityAlertsPage',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const emptyWelcomeConvo: Conversation = {
|
||||
id: '',
|
||||
title: 'Welcome',
|
||||
|
@ -47,6 +61,11 @@ export const emptyWelcomeConvo: Conversation = {
|
|||
},
|
||||
};
|
||||
|
||||
export const conversationWithContentReferences: Conversation = {
|
||||
...emptyWelcomeConvo,
|
||||
messages: [messageWithContentReferences],
|
||||
};
|
||||
|
||||
export const welcomeConvo: Conversation = {
|
||||
...emptyWelcomeConvo,
|
||||
messages: [
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { AnonymizedValuesAndCitationsTour } from '.';
|
||||
import React from 'react';
|
||||
import useLocalStorage from 'react-use/lib/useLocalStorage';
|
||||
import {
|
||||
alertConvo,
|
||||
conversationWithContentReferences,
|
||||
welcomeConvo,
|
||||
} from '../../mock/conversation';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { TourState } from '../knowledge_base';
|
||||
|
||||
jest.mock('react-use/lib/useLocalStorage', () => jest.fn());
|
||||
|
||||
jest.mock('lodash', () => ({
|
||||
...jest.requireActual('lodash'),
|
||||
throttle: jest.fn().mockImplementation((fn) => fn),
|
||||
}));
|
||||
|
||||
const mockGetItem = jest.fn();
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: {
|
||||
getItem: (...args: string[]) => mockGetItem(...args),
|
||||
},
|
||||
});
|
||||
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<I18nProvider>
|
||||
<div>
|
||||
<div id="aiAssistantSettingsMenuContainer" />
|
||||
{children}
|
||||
</div>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
describe('AnonymizedValuesAndCitationsTour', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it('renders tour when there are content references', async () => {
|
||||
(useLocalStorage as jest.Mock).mockReturnValue([false, jest.fn()]);
|
||||
|
||||
mockGetItem.mockReturnValue(
|
||||
JSON.stringify({
|
||||
currentTourStep: 2,
|
||||
isTourActive: true,
|
||||
} as TourState)
|
||||
);
|
||||
|
||||
render(<AnonymizedValuesAndCitationsTour conversation={conversationWithContentReferences} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('anonymizedValuesAndCitationsTourStep')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('anonymizedValuesAndCitationsTourStepPanel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders tour when there are replacements', async () => {
|
||||
(useLocalStorage as jest.Mock).mockReturnValue([false, jest.fn()]);
|
||||
|
||||
mockGetItem.mockReturnValue(
|
||||
JSON.stringify({
|
||||
currentTourStep: 2,
|
||||
isTourActive: true,
|
||||
} as TourState)
|
||||
);
|
||||
|
||||
render(<AnonymizedValuesAndCitationsTour conversation={alertConvo} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('anonymizedValuesAndCitationsTourStep')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('anonymizedValuesAndCitationsTourStepPanel')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render tour if it has already been shown', async () => {
|
||||
(useLocalStorage as jest.Mock).mockReturnValue([true, jest.fn()]);
|
||||
|
||||
mockGetItem.mockReturnValue(
|
||||
JSON.stringify({
|
||||
currentTourStep: 2,
|
||||
isTourActive: true,
|
||||
} as TourState)
|
||||
);
|
||||
|
||||
render(<AnonymizedValuesAndCitationsTour conversation={alertConvo} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('anonymizedValuesAndCitationsTourStep')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('anonymizedValuesAndCitationsTourStepPanel')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render tour if the knowledge base tour is on step 1', async () => {
|
||||
(useLocalStorage as jest.Mock).mockReturnValue([false, jest.fn()]);
|
||||
|
||||
mockGetItem.mockReturnValue(
|
||||
JSON.stringify({
|
||||
currentTourStep: 1,
|
||||
isTourActive: true,
|
||||
} as TourState)
|
||||
);
|
||||
|
||||
render(<AnonymizedValuesAndCitationsTour conversation={conversationWithContentReferences} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('anonymizedValuesAndCitationsTourStep')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('anonymizedValuesAndCitationsTourStepPanel')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render tour if there are no content references or replacements', async () => {
|
||||
(useLocalStorage as jest.Mock).mockReturnValue([false, jest.fn()]);
|
||||
|
||||
mockGetItem.mockReturnValue(
|
||||
JSON.stringify({
|
||||
currentTourStep: 2,
|
||||
isTourActive: true,
|
||||
} as TourState)
|
||||
);
|
||||
|
||||
render(<AnonymizedValuesAndCitationsTour conversation={welcomeConvo} />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('anonymizedValuesAndCitationsTourStep')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('anonymizedValuesAndCitationsTourStepPanel')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiTourStep } from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { isEmpty, throttle } from 'lodash';
|
||||
import useLocalStorage from 'react-use/lib/useLocalStorage';
|
||||
import { Conversation } from '../../assistant_context/types';
|
||||
import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '../const';
|
||||
import { anonymizedValuesAndCitationsTourStep1 } from './step_config';
|
||||
import { TourState } from '../knowledge_base';
|
||||
|
||||
interface Props {
|
||||
conversation: Conversation | undefined;
|
||||
}
|
||||
|
||||
// Throttles reads from local storage to 1 every 5 seconds.
|
||||
// This is to prevent excessive reading from local storage. It acts
|
||||
// as a cache.
|
||||
const getKnowledgeBaseTourStateThrottled = throttle(() => {
|
||||
const value = localStorage.getItem(NEW_FEATURES_TOUR_STORAGE_KEYS.KNOWLEDGE_BASE);
|
||||
if (value) {
|
||||
return JSON.parse(value) as TourState;
|
||||
}
|
||||
return undefined;
|
||||
}, 5000);
|
||||
|
||||
export const AnonymizedValuesAndCitationsTour: React.FC<Props> = ({ conversation }) => {
|
||||
const [tourCompleted, setTourCompleted] = useLocalStorage<boolean>(
|
||||
NEW_FEATURES_TOUR_STORAGE_KEYS.ANONYMIZED_VALUES_AND_CITATIONS,
|
||||
false
|
||||
);
|
||||
|
||||
const [showTour, setShowTour] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (showTour || !conversation || tourCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const knowledgeBaseTourState = getKnowledgeBaseTourStateThrottled();
|
||||
|
||||
// If the knowledge base tour is active on this page (i.e. step 1), don't show this tour to prevent overlap.
|
||||
if (knowledgeBaseTourState?.isTourActive && knowledgeBaseTourState?.currentTourStep === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const containsContentReferences = conversation.messages.some(
|
||||
(message) => !isEmpty(message.metadata?.contentReferences)
|
||||
);
|
||||
const containsReplacements = !isEmpty(conversation.replacements);
|
||||
|
||||
if (containsContentReferences || containsReplacements) {
|
||||
const timer = setTimeout(() => {
|
||||
setShowTour(true);
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}
|
||||
}, [conversation, tourCompleted, showTour]);
|
||||
|
||||
const finishTour = useCallback(() => {
|
||||
setTourCompleted(true);
|
||||
setShowTour(false);
|
||||
}, [setTourCompleted, setShowTour]);
|
||||
|
||||
return (
|
||||
<EuiTourStep
|
||||
data-test-subj="anonymizedValuesAndCitationsTourStep"
|
||||
panelProps={{
|
||||
'data-test-subj': `anonymizedValuesAndCitationsTourStepPanel`,
|
||||
}}
|
||||
anchor={anonymizedValuesAndCitationsTourStep1.anchor}
|
||||
content={anonymizedValuesAndCitationsTourStep1.content}
|
||||
isStepOpen={showTour}
|
||||
maxWidth={300}
|
||||
onFinish={finishTour}
|
||||
step={1}
|
||||
stepsTotal={1}
|
||||
title={anonymizedValuesAndCitationsTourStep1.title}
|
||||
subtitle={anonymizedValuesAndCitationsTourStep1.subTitle}
|
||||
anchorPosition="rightUp"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiText, EuiSpacer } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { AI_ASSISTANT_SETTINGS_MENU_CONTAINER_ID } from '../../assistant/assistant_header';
|
||||
|
||||
const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
|
||||
|
||||
export const anonymizedValuesAndCitationsTourStep1 = {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id="xpack.elasticAssistant.anonymizedValuesAndCitations.tour.title"
|
||||
defaultMessage="Citations & Anonymized values"
|
||||
/>
|
||||
),
|
||||
subTitle: (
|
||||
<FormattedMessage
|
||||
id="xpack.elasticAssistant.anonymizedValuesAndCitations.tour.subtitle"
|
||||
defaultMessage="New and improved!"
|
||||
/>
|
||||
),
|
||||
anchor: `#${AI_ASSISTANT_SETTINGS_MENU_CONTAINER_ID}`,
|
||||
content: (
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.elasticAssistant.anonymizedValuesAndCitations.tour.content.citedKnowledgeBaseEntries"
|
||||
defaultMessage="<bold>Cited</bold> Knowledge base entries show in the chat stream. Toggle on or off in the menu or with the shortcut: {keyboardShortcut}."
|
||||
values={{
|
||||
keyboardShortcut: isMac ? '⌥ c' : 'Alt c',
|
||||
bold: (str) => <strong>{str}</strong>,
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<FormattedMessage
|
||||
id="xpack.elasticAssistant.anonymizedValuesAndCitations.tour.content.anonymizedValues"
|
||||
defaultMessage="The toggle to show or hide <bold>Anonymized values</bold> in the chat stream, has moved to the menu. Use the shortcut: {keyboardShortcut}. Your data is still sent anonymized to the LLM based on the settings in the Anonymization panel."
|
||||
values={{
|
||||
keyboardShortcut: isMac ? '⌥ a' : 'Alt a',
|
||||
bold: (str) => <strong>{str}</strong>,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
),
|
||||
};
|
|
@ -7,4 +7,6 @@
|
|||
|
||||
export const NEW_FEATURES_TOUR_STORAGE_KEYS = {
|
||||
KNOWLEDGE_BASE: 'elasticAssistant.knowledgeBase.newFeaturesTour.v8.16',
|
||||
ANONYMIZED_VALUES_AND_CITATIONS:
|
||||
'elasticAssistant.anonymizedValuesAndCitationsTourCompleted.v8.18',
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '../const';
|
|||
import { knowledgeBaseTourStepOne, tourConfig } from './step_config';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface TourState {
|
||||
export interface TourState {
|
||||
currentTourStep: number;
|
||||
isTourActive: boolean;
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ export const ContentReferenceParser: Plugin = function ContentReferenceParser()
|
|||
|
||||
const contentReferenceId = readArg('(', ')');
|
||||
|
||||
const closeChar = value[index++];
|
||||
const closeChar = value[index];
|
||||
if (closeChar !== '}') return false;
|
||||
|
||||
const now = eat.now();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue