Skip to content

Commit

Permalink
Merge pull request #1346 from akto-api-security/improve/dashboard_per…
Browse files Browse the repository at this point in the history
…formance

Improving dashboard speed
  • Loading branch information
notshivansh authored Aug 12, 2024
2 parents e909baf + d3ea87f commit 828820b
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 79 deletions.
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
import { TopBar, Icon, Text, ActionList, Modal, TextField, HorizontalStack, Box, Avatar, VerticalStack, Button } from '@shopify/polaris';
import { NotificationMajor, CustomerPlusMajor, LogOutMinor, NoteMinor, ResourcesMajor, UpdateInventoryMajor, PageMajor, DynamicSourceMajor } from '@shopify/polaris-icons';
import { useState, useCallback, useEffect } from 'react';
import { useState, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import Store from '../../../store';
import PersistStore from '../../../../main/PersistStore';
import './Headers.css'
import api from '../../../../signup/api';
import func from '@/util/func';
import SemiCircleProgress from '../../shared/SemiCircleProgress';
import testingApi from "../../../pages/testing/api"
import TestingStore from '../../../pages/testing/testingStore';
import { usePolling } from '../../../../main/PollingProvider';
import { debounce } from 'lodash';

function ContentWithIcon({icon,text, isAvatar= false}) {
return(
<HorizontalStack gap={2}>
<Box width='20px'>
{isAvatar ? <div className='reduce-size'><Avatar size="extraSmall" source={icon} /> </div>:
<Icon source={icon} color="base" />}
</Box>
<Text>{text}</Text>
</HorizontalStack>
)
}

export default function Header() {
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
const [isSearchActive, setIsSearchActive] = useState(false);
const [searchValue, setSearchValue] = useState('');
const [newAccount, setNewAccount] = useState('')
const [showCreateAccount, setShowCreateAccount] = useState(false)
const [currentTestsObj, setCurrentTestsObj] = useState({
totalTestsCompleted:0,
totalTestsInitiated:0,
totalTestsQueued: 0,
testRunsArr: [],
})

const { currentTestsObj, clearPollingInterval } = usePolling();
const navigate = useNavigate()

const username = Store((state) => state.username)
Expand All @@ -34,18 +39,15 @@ export default function Header() {

const allRoutes = Store((state) => state.allRoutes)
const allCollections = PersistStore((state) => state.allCollections)
const searchItemsArr = func.getSearchItemsArr(allRoutes, allCollections)

const setCurrentTestingRuns = TestingStore(state => state.setCurrentTestingRuns)
const [intervalId, setIntervalId] = useState(null);

const searchItemsArr = useMemo(() => func.getSearchItemsArr(allRoutes, allCollections), [])
const [filteredItemsArr, setFilteredItemsArr] = useState(searchItemsArr)
const toggleIsUserMenuOpen = useCallback(
() => setIsUserMenuOpen((isUserMenuOpen) => !isUserMenuOpen),
[],
);

const handleLogOut = async () => {
clearInterval(intervalId)
clearPollingInterval()
api.logout().then(res => {
resetAll();
storeAccessToken(null)
Expand All @@ -59,7 +61,15 @@ export default function Header() {
})
}


const debouncedSearch = debounce((searchQuery) => {
if(searchQuery.length === 0){
setFilteredItemsArr(searchItemsArr)
}else{
const resultArr = searchItemsArr.filter((x) => x.content.toLowerCase().includes(searchQuery))
setFilteredItemsArr(resultArr)
}
}, 500);

const accountsItems = Object.keys(accounts).map(accountId => {
return {
id: accountId,
Expand All @@ -81,18 +91,6 @@ export default function Header() {
window.location.href="/dashboard/onboarding"
})
}

function ContentWithIcon({icon,text, isAvatar= false}) {
return(
<HorizontalStack gap={2}>
<Box width='20px'>
{isAvatar ? <div className='reduce-size'><Avatar size="extraSmall" source={icon} /> </div>:
<Icon source={icon} color="base" />}
</Box>
<Text>{text}</Text>
</HorizontalStack>
)
}

const userMenuMarkup = (
<TopBar.UserMenu
Expand Down Expand Up @@ -124,22 +122,17 @@ export default function Header() {
/>
);

const handleSearchResultsDismiss = useCallback(() => {
setIsSearchActive(false);
setSearchValue('');
}, []);

const handleSearchChange = useCallback((value) => {
setSearchValue(value);
setIsSearchActive(value.length > 0);
debouncedSearch(value.toLowerCase())
}, []);

const handleNavigateSearch = (url) => {
navigate(url)
handleSearchResultsDismiss()
handleSearchChange('')
}

const searchItems = searchItemsArr.map((item) => {
const searchItems = filteredItemsArr.slice(0,20).map((item) => {
const icon = item.type === 'page' ? PageMajor : DynamicSourceMajor;
return {
value: item.content,
Expand All @@ -150,7 +143,7 @@ export default function Header() {

const searchResultsMarkup = (
<ActionList
items={searchItems.filter(x => x.value.toLowerCase().includes(searchValue.toLowerCase()))}
items={searchItems}
/>
);

Expand All @@ -171,7 +164,10 @@ export default function Header() {
navigate(navUrl)
}

const progress = currentTestsObj.totalTestsInitiated === 0 ? 0 : Math.floor((currentTestsObj.totalTestsCompleted * 100)/ currentTestsObj.totalTestsInitiated)
const progress = useMemo(() => {
return currentTestsObj.totalTestsInitiated === 0 ? 0 : Math.floor((currentTestsObj.totalTestsCompleted * 100) / currentTestsObj.totalTestsInitiated);
}, [currentTestsObj.totalTestsCompleted, currentTestsObj.totalTestsInitiated]);


const secondaryMenuMarkup = (
<HorizontalStack gap={"4"}>
Expand Down Expand Up @@ -203,9 +199,9 @@ export default function Header() {
showNavigationToggle
userMenu={userMenuMarkup}
searchField={searchFieldMarkup}
searchResultsVisible={isSearchActive}
searchResultsVisible={searchValue.length > 0}
searchResults={searchResultsMarkup}
onSearchResultsDismiss={handleSearchResultsDismiss}
onSearchResultsDismiss={() =>handleSearchChange('')}
secondaryMenu={secondaryMenuMarkup}
/>
<Modal
Expand Down Expand Up @@ -237,30 +233,6 @@ export default function Header() {
</div>
);

useEffect(() => {
const fetchTestingStatus = () => {
const id = setInterval(() => {
testingApi.fetchTestingRunStatus().then((resp) => {
setCurrentTestingRuns(resp.currentRunningTestsStatus);
setCurrentTestsObj({
totalTestsInitiated: resp?.testRunsScheduled || 0,
totalTestsCompleted: resp?.totalTestsCompleted || 0,
totalTestsQueued: resp?.testRunsQueued || 0,
testRunsArr: resp?.currentRunningTestsStatus || []
});
});
}, 2000);
setIntervalId(id);
};

fetchTestingStatus();

return () => {
clearInterval(intervalId);
};
}, []);


return (
topBarMarkup
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "../settings.css"
import settingFunctions from '../module'
import func from "@/util/func"
import PersistStore from '../../../../main/PersistStore'
import { debounce } from 'lodash'

function AktoGPT() {

Expand All @@ -23,7 +24,7 @@ function AktoGPT() {
}

useEffect(()=> {
setDisplayItems(apiCollections)
setDisplayItems(apiCollections.slice(0,30))
},[apiCollections])

useEffect(()=>{
Expand Down Expand Up @@ -58,7 +59,7 @@ function AktoGPT() {

const sortItems = () =>{
setSelectedItems([])
const arr = [...displayItems].sort((a, b) => {
const arr = [...apiCollections].sort((a, b) => {
let aComp = a.displayName.replace(/[^a-zA-Z]/g, '').toLowerCase();
let bComp = b.displayName.replace(/[^a-zA-Z]/g, '').toLowerCase();
if (aComp < bComp) return -1;
Expand All @@ -69,7 +70,7 @@ function AktoGPT() {
arr.reverse()
}
setSortOrder(!sortOrder)
setDisplayItems(arr)
setDisplayItems(arr.slice(0,30))
setTimeout(() => {
setSelectedItems(clonedItems);
}, 0)
Expand All @@ -78,18 +79,23 @@ function AktoGPT() {
<Button icon={SortMinor} onClick={sortItems}>Sort</Button>
)

const debouncedSearch = debounce((searchQuery) => {
let localVar = selectedItems
setSelectedItems([])
if(searchQuery.length === 0){
setDisplayItems(apiCollections.slice(0,30))
}else{
const resultArr = apiCollections.filter((x) => x?.displayName.toLowerCase().includes(searchQuery))
setDisplayItems(resultArr.slice(0,30))
setTimeout(() => {
setSelectedItems(localVar)
},0)
}
}, 500);

const searchResult = (item) =>{
setSearchValue(item)
let localVar = selectedItems;
setSelectedItems([])
const filterRegex = new RegExp(item, 'i');
const resultOptions = apiCollections.filter((option) =>
option.displayName.match(filterRegex)
);
setDisplayItems(resultOptions)
setTimeout(() => {
setSelectedItems(localVar);
}, 0)
debouncedSearch(item)
}

const SearchIcon = (
Expand Down
3 changes: 3 additions & 0 deletions apps/dashboard/web/polaris_web/web/src/apps/main/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import PageBusinessEmail from "../signup/pages/PageBusinessEmail"
import TokenValidator from "./TokenValidator"
import { TableContextProvider } from "@/apps/dashboard/components/tables/TableContext";
import VulnerabilityReport from "../dashboard/pages/testing/vulnerability_report/VulnerabilityReport";
import { PollingProvider } from "./PollingProvider";

// if you add a component in a new path, please verify the search implementation in function -> 'getSearchItemsArr' in func.js

Expand Down Expand Up @@ -360,9 +361,11 @@ function App() {
}, [])

return (
<PollingProvider>
<TableContextProvider>
<RouterProvider router={router} />
</TableContextProvider>
</PollingProvider>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { createContext, useContext, useEffect, useState, useRef } from 'react';
import testingApi from "../dashboard/pages/testing/api"
import TestingStore from '../dashboard/pages/testing/testingStore';
const PollingContext = createContext();

export const usePolling = () => useContext(PollingContext);

export const PollingProvider = ({ children }) => {
const [currentTestsObj, setCurrentTestsObj] = useState({
totalTestsCompleted: 0,
totalTestsInitiated: 0,
totalTestsQueued: 0,
testRunsArr: [],
});

const intervalIdRef = useRef(null);
const setCurrentTestingRuns = TestingStore(state => state.setCurrentTestingRuns)

useEffect(() => {
const fetchTestingStatus = () => {
const id = setInterval(() => {
testingApi.fetchTestingRunStatus().then((resp) => {
setCurrentTestingRuns(resp?.currentRunningTestsStatus)
setCurrentTestsObj(prevState => {
const newTestsObj = {
totalTestsInitiated: resp?.testRunsScheduled || 0,
totalTestsCompleted: resp?.totalTestsCompleted || 0,
totalTestsQueued: resp?.testRunsQueued || 0,
testRunsArr: resp?.currentRunningTestsStatus || []
};
if (JSON.stringify(prevState) !== JSON.stringify(newTestsObj)) {
return newTestsObj;
}
return prevState;
});
});
}, 2000);
intervalIdRef.current = id;
};

fetchTestingStatus();

return () => {
clearInterval(intervalIdRef.current);
};
}, []);

const clearPollingInterval = () => {
clearInterval(intervalIdRef.current);
};

return (
<PollingContext.Provider value={{ currentTestsObj, clearPollingInterval }}>
{children}
</PollingContext.Provider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import api from '../api'
import func from '@/util/func'
import "../styles.css"
import Store from '../../dashboard/store'
import PersistStore from '../../main/PersistStore'
import { usePolling } from '../../main/PollingProvider'

function SignUp() {

Expand All @@ -22,6 +24,8 @@ function SignUp() {
const azureUrl = window.AZURE_REQUEST_URL
const githubId = window.GITHUB_CLIENT_ID
const githubUrl = window.GITHUB_URL ? window.GITHUB_URL : "https://github.com"
const resetAll = PersistStore(state => state.resetAll)
const { clearPollingInterval } = usePolling();

const githubAuthObj = {
logo: '/public/github_icon.svg',
Expand All @@ -42,6 +46,8 @@ function SignUp() {
}

useEffect(() => {
resetAll()
clearPollingInterval()
let copySsoList = []
if (githubId !== undefined && githubId.length > 0) {
copySsoList.push(githubAuthObj)
Expand Down

0 comments on commit 828820b

Please sign in to comment.