From 8f15f7e1453f5aac892fe4b9576b2ff0ee7c864f Mon Sep 17 00:00:00 2001 From: James Habben Date: Thu, 9 Nov 2023 10:35:32 -0800 Subject: [PATCH 1/3] implement heap analytics --- src/App.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/App.js b/src/App.js index f40a55b..1d83c6c 100644 --- a/src/App.js +++ b/src/App.js @@ -10,12 +10,43 @@ import { AuthProvider } from './AuthContext'; import './App.css'; +/* global heap */ + function App() { const [showScroll, setShowScroll] = useState(false); + const loadHeap = (heapId) => { + window.heap = window.heap || []; + window.heap.load = function (e, t) { + window.heap.appid = e; + window.heap.config = t = t || {}; + const r = document.createElement("script"); + r.type = "text/javascript"; + r.async = true; + r.src = `https://cdn.heapanalytics.com/js/heap-${e}.js`; + const a = document.getElementsByTagName("script")[0]; + a.parentNode.insertBefore(r, a); + for (const o of ["addEventProperties", "addUserProperties", "clearEventProperties", "identify", "resetIdentity", "removeEventProperty", "setEventProperties", "track", "unsetEventProperty"]) { + window.heap[o] = (...args) => { + window.heap.push([o].concat(args)); + }; + } + }; + window.heap.load(heapId); + }; + useEffect(() => { - if (process.env.NODE_ENV === 'development') { - document.title = document.title + ' (Dev Mode)'; + const heapId = process.env.NODE_ENV === 'development' + ? process.env.REACT_APP_HEAP_ID + : process.env.REACT_APP_HEAP_ID; + + if (document.title && process.env.NODE_ENV === 'development') { + document.title += ' (Dev Mode)'; + } + + if (heapId) { + console.log('Heap ID:', heapId); + loadHeap(heapId); } }, []); From 6b8cc03f716f627245c3b8aaaeada68873d6996a Mon Sep 17 00:00:00 2001 From: James Habben Date: Thu, 9 Nov 2023 10:38:28 -0800 Subject: [PATCH 2/3] env files adjust --- .env.example | 1 + .gitignore | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6841694 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +REACT_APP_HEAP_ID=123_replace_456 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4d29575..e9d0a73 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,10 @@ # misc .DS_Store .env.local +.env.development .env.development.local .env.test.local +.env.production .env.production.local npm-debug.log* From 5eaf08845d258cbe5e7e8393d80f9416f9173355 Mon Sep 17 00:00:00 2001 From: James Habben Date: Fri, 10 Nov 2023 07:11:09 -0800 Subject: [PATCH 3/3] further heap updates --- package-lock.json | 1 + package.json | 1 + public/version-build.json | 2 +- src/components/AppDetails.js | 5 ++ src/components/PageSearch.js | 77 +++++++++++++++---- .../searchCompnents/RecentAppsCard.js | 12 ++- .../searchCompnents/WhatsNewTile.js | 1 + src/services/githubService.js | 22 +++--- 8 files changed, 94 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4aa11d6..55df73c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "antd": "^5.9.4", "fast-json-patch": "^3.1.1", "js-base64": "^3.7.5", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^9.0.0", diff --git a/package.json b/package.json index a9c338b..e8ed97e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "antd": "^5.9.4", "fast-json-patch": "^3.1.1", "js-base64": "^3.7.5", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^9.0.0", diff --git a/public/version-build.json b/public/version-build.json index a105387..584fd85 100644 --- a/public/version-build.json +++ b/public/version-build.json @@ -1,3 +1,3 @@ { - "buildDate": "2023-11-02" + "buildDate": "2023-11-09" } \ No newline at end of file diff --git a/src/components/AppDetails.js b/src/components/AppDetails.js index c1ad260..e1520bb 100644 --- a/src/components/AppDetails.js +++ b/src/components/AppDetails.js @@ -16,8 +16,11 @@ function AppDetails({ app, tools }) { const [artifacts, setArtifacts] = useState([]); const toolRefs = ({}); + + useEffect(() => { //console.log("Tools in AppDetails:", tools); + window.heap.track('App View', { appName: app.appName }) if (tools && apps && tools.length > 0) { //console.log("Fetching artifacts for app:", app.appName, "with tools:", tools); @@ -43,6 +46,7 @@ function AppDetails({ app, tools }) { const handleIconClick = (toolShortName) => { //console.log(toolRefs) + window.heap.track('App Tool Jump', { toolName: toolShortName }) const ref = toolRefs.current[toolShortName]; if (ref && ref.current) { ref.current.scrollIntoView({ behavior: 'smooth' }); @@ -77,6 +81,7 @@ function AppDetails({ app, tools }) { return (
+ {}
{ - setSuggestionValues(); - }, [searchTerm, apps]); function parseSearchTerms(term) { const result = { @@ -102,9 +100,42 @@ function PageSearch() { return result; } + const [filteredApps, setFilteredApps] = useState([]); + const [isFiltering, setIsFiltering] = useState(false); // New state to track if we are currently filtering + const debouncedFilterAppsRef = useRef(); - const filteredApps = useMemo(() => { - if (!searchTerm) return []; + useEffect(() => { + debouncedFilterAppsRef.current = debounce((search) => { + const { operator, property, value } = parseSearchTerms(search); + + const filtered = apps.filter(app => { + // Initial filter based on '-no:' operator + let matchesOperator = true; + if (operator === '-no:' && property) { + matchesOperator = !app[property]; // Check if the property value is falsy + } + + // Further filter the reduced set based on the search term + if (matchesOperator && value) { + const lowerValue = value.toLowerCase(); + return ( + app.appName.toLowerCase().includes(lowerValue) || + (app.alternateNames && app.alternateNames.some(name => name.toLowerCase().includes(lowerValue))) + ); + } + + return matchesOperator; // If there's no value, return the result of the operator check + }); + + setFilteredApps(filtered); + setIsFiltering(false); // Set filtering to false once done + window.heap.track('Search', { searchTerm: value }); // Track the search term with Heap here + }, 300); + }, [apps]); + + // const filteredApps = useMemo(() => { + const filteredAppsold = () => { + if (!searchTerm) return []; const { operator, property, value } = parseSearchTerms(searchTerm); @@ -126,7 +157,7 @@ function PageSearch() { return matchesOperator; // If there's no value, return the result of the operator check }); - }, [searchTerm, apps]); + }; // () [searchTerm, apps]; @@ -140,6 +171,18 @@ function PageSearch() { onClick={clearSearch} /> ); + + useEffect(() => { + setSuggestionValues(); + if (searchTerm) { + setIsFiltering(true); + debouncedFilterAppsRef.current(searchTerm); + } else { + setIsFiltering(false); + setFilteredApps([]); + } + }, [searchTerm, debouncedFilterAppsRef]); + return (
@@ -168,10 +211,18 @@ function PageSearch() { />
- {searchTerm - ? `${filteredApps.length} matching apps` - : `${apps.length} apps and ${tools.length} forensic tools in the database. You can `} - {!searchTerm && contribute}! + {isFiltering ? ( + + Searching... + + ) : searchTerm ? ( + `${filteredApps.length} matching apps` + ) : ( + + {`${apps.length} apps and ${tools.length} forensic tools in the database. You can `} + contribute! + + )}
{!searchTerm && ( <> diff --git a/src/components/searchCompnents/RecentAppsCard.js b/src/components/searchCompnents/RecentAppsCard.js index 0201418..49c7224 100644 --- a/src/components/searchCompnents/RecentAppsCard.js +++ b/src/components/searchCompnents/RecentAppsCard.js @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { Card, Row, Col } from 'antd'; import AppTile from 'components/searchCompnents/AppTile'; @@ -16,13 +16,21 @@ function RecentAppsCard({ apps, tools, onAppClick }) { return sortedApps.slice(0, 6); // Get the six most recent apps }, [sortedApps]); + const handleTileClick = useCallback((app) => { + window.heap.track('Recent App Clicked', { appName: app.name }); + + if (onAppClick) { + onAppClick(app); + } + }, [onAppClick]); + return (

Recent App Updates

{recentApps.map((app, index) => ( - + handleTileClick(app)} /> ))} diff --git a/src/components/searchCompnents/WhatsNewTile.js b/src/components/searchCompnents/WhatsNewTile.js index 697ffe3..09db6cd 100644 --- a/src/components/searchCompnents/WhatsNewTile.js +++ b/src/components/searchCompnents/WhatsNewTile.js @@ -20,6 +20,7 @@ function WhatsNewTile() { }, []); const showModal = () => { + window.heap.track('Whats New More', { }) setIsModalVisible(true); }; diff --git a/src/services/githubService.js b/src/services/githubService.js index a604846..00d2d1f 100644 --- a/src/services/githubService.js +++ b/src/services/githubService.js @@ -336,7 +336,7 @@ export const githubService = (token, username) => { }); terminal(prevOutput => [...prevOutput, `Blob created: ${blobSha}`]); - terminal(prevOutput => [...prevOutput, 'Fetching latest commit SHA from events...']); + // terminal(prevOutput => [...prevOutput, 'Fetching latest commit SHA from events...']); // const { data: { object: { sha: latestCommitSha } } } = await octokit.git.getRef({ // owner: username, // repo: repo, @@ -366,16 +366,16 @@ export const githubService = (token, username) => { latestCommitSha = latestCommitCache.sha; // Use cached SHA if it's within the last minute terminal(prevOutput => [...prevOutput, `Using session last commit SHA: ${latestCommitSha}`]); } else { - try { - const response = await octokit.activity.listRepoEvents({ - owner: username, - repo: repo, - per_page: 1 - }); - latestCommitSha = response.data[0]?.payload?.head; // Assuming the latest event is a push event - } catch (error) { - terminal(prevOutput => [...prevOutput, `Error fetching latest commit SHA from Events API: ${error.message}`]); - } + // try { + // const response = await octokit.activity.listRepoEvents({ + // owner: username, + // repo: repo, + // per_page: 1 + // }); + // latestCommitSha = response.data[0]?.payload?.head; // Assuming the latest event is a push event + // } catch (error) { + // terminal(prevOutput => [...prevOutput, `Error fetching latest commit SHA from Events API: ${error.message}`]); + // } if (!latestCommitSha) {