Skip to content

Commit 9eac42c

Browse files
feat: Add recently visited repo at top of list (#3734)
1 parent 3f72266 commit 9eac42c

File tree

10 files changed

+666
-7
lines changed

10 files changed

+666
-7
lines changed

src/shared/ListRepo/RepoTitleLink/RepoTitleLink.jsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import PropTypes from 'prop-types'
22

33
import AppLink from 'shared/AppLink'
4+
import { transformStringToLocalStorageKey } from 'shared/utils/transformStringToLocalStorageKey'
45
import Icon from 'ui/Icon'
56
import Label from 'ui/Label'
67

78
const getRepoIconName = ({ activated, isRepoPrivate, active }) =>
89
!activated && active ? 'ban' : isRepoPrivate ? 'lock-closed' : 'globe-alt'
910

10-
function RepoTitleLink({ repo, showRepoOwner, pageName, disabledLink }) {
11+
function RepoTitleLink({
12+
repo,
13+
showRepoOwner,
14+
pageName,
15+
disabledLink,
16+
isRecentlyVisited,
17+
}) {
1118
const options = {
1219
owner: repo.author.username,
1320
repo: repo.name,
@@ -49,6 +56,12 @@ function RepoTitleLink({ repo, showRepoOwner, pageName, disabledLink }) {
4956
pageName={pageName}
5057
options={options}
5158
className="flex items-center text-ds-gray-quinary hover:underline"
59+
onClick={() => {
60+
if (repo?.name && repo?.author?.username && repo?.active) {
61+
const key = transformStringToLocalStorageKey(repo.author.username)
62+
localStorage.setItem(`${key}_recently_visited`, repo.name)
63+
}
64+
}}
5265
>
5366
<Icon
5467
size="sm"
@@ -75,6 +88,11 @@ function RepoTitleLink({ repo, showRepoOwner, pageName, disabledLink }) {
7588
System generated
7689
</Label>
7790
)}
91+
{isRecentlyVisited && (
92+
<Label variant="plain" className="ml-2">
93+
Recently visited
94+
</Label>
95+
)}
7896
</div>
7997
)
8098
}
@@ -93,6 +111,7 @@ RepoTitleLink.propTypes = {
93111
showRepoOwner: PropTypes.bool.isRequired,
94112
pageName: PropTypes.string.isRequired,
95113
disabledLink: PropTypes.bool.isRequired,
114+
isRecentlyVisited: PropTypes.bool,
96115
}
97116

98117
export default RepoTitleLink

src/shared/ListRepo/ReposTable/ReposTable.test.tsx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { setupServer } from 'msw/node'
1616
import { mockIsIntersecting } from 'react-intersection-observer/test-utils'
1717
import { MemoryRouter, Route } from 'react-router-dom'
1818

19+
import { transformStringToLocalStorageKey } from 'shared/utils/transformStringToLocalStorageKey'
20+
1921
import ReposTable from './ReposTable'
2022

2123
const mockRepositories = (
@@ -1212,4 +1214,96 @@ describe('ReposTable', () => {
12121214
expect(demoLink.length).toBe(0)
12131215
})
12141216
})
1217+
1218+
describe('handles recently visited repo', () => {
1219+
beforeEach(() => {
1220+
setup({})
1221+
localStorage.clear()
1222+
localStorage.setItem(
1223+
`${transformStringToLocalStorageKey('owner1')}_recently_visited`,
1224+
'gazebo'
1225+
)
1226+
server.use(
1227+
graphql.query('ReposForOwner', async (info) => {
1228+
const recentlyVisitedRepo = [
1229+
{
1230+
node: {
1231+
private: false,
1232+
activated: true,
1233+
author: {
1234+
username: 'owner1',
1235+
},
1236+
name: 'gazebo',
1237+
latestCommitAt: subDays(new Date(), 3).toISOString(),
1238+
coverageAnalytics: {
1239+
percentCovered: 0,
1240+
lines: 123,
1241+
},
1242+
active: true,
1243+
updatedAt: '2020-08-25T16:36:19.67986800:00',
1244+
repositoryConfig: null,
1245+
coverageEnabled: true,
1246+
bundleAnalysisEnabled: true,
1247+
},
1248+
},
1249+
]
1250+
1251+
const myRepos = [
1252+
{
1253+
node: {
1254+
private: false,
1255+
activated: false,
1256+
author: {
1257+
username: 'owner1',
1258+
},
1259+
name: 'Repo name 1',
1260+
latestCommitAt: subDays(new Date(), 3).toISOString(),
1261+
coverageAnalytics: {
1262+
percentCovered: 10,
1263+
lines: 123,
1264+
},
1265+
active: true,
1266+
updatedAt: '2020-08-25T16:36:19.67986800:00',
1267+
repositoryConfig: null,
1268+
coverageEnabled: true,
1269+
bundleAnalysisEnabled: false,
1270+
},
1271+
},
1272+
]
1273+
1274+
let reposToReturn = myRepos.filter(
1275+
(repo) =>
1276+
!info.variables.filters.term ||
1277+
repo.node.name.includes(info.variables.filters.term)
1278+
)
1279+
1280+
if (info.variables.filters.repoNames) {
1281+
reposToReturn = recentlyVisitedRepo
1282+
}
1283+
1284+
return HttpResponse.json({
1285+
data: {
1286+
owner: {
1287+
repositories: {
1288+
edges: reposToReturn,
1289+
pageInfo: {
1290+
hasNextPage: false,
1291+
endCursor: '3',
1292+
},
1293+
},
1294+
},
1295+
},
1296+
})
1297+
})
1298+
)
1299+
})
1300+
1301+
it('shows recently visited repo', async () => {
1302+
render(<ReposTable searchValue="" owner="owner1" />, {
1303+
wrapper: wrapper('/github/owner1', '/:provider/:owner'),
1304+
})
1305+
const recentlyVisitedRepo = await screen.findByText(/Recently visited/)
1306+
expect(recentlyVisitedRepo).toBeInTheDocument()
1307+
})
1308+
})
12151309
})

src/shared/ListRepo/ReposTable/ReposTable.tsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
import { useIsTeamPlan } from 'services/useIsTeamPlan'
2222
import { useOwner, useUser } from 'services/user'
2323
import { DEMO_REPO, formatDemoRepos, isNotNull } from 'shared/utils/demo'
24+
import { getFilteredRecentlyVisitedRepo } from 'shared/utils/getFilteredRecentlyVisitedRepo'
25+
import { transformStringToLocalStorageKey } from 'shared/utils/transformStringToLocalStorageKey'
2426
import Icon from 'ui/Icon'
2527
import Spinner from 'ui/Spinner'
2628

@@ -147,6 +149,18 @@ const ReposTable = ({
147149
})
148150
)
149151

152+
const recentlyVisitedRepoName = localStorage.getItem(
153+
`${transformStringToLocalStorageKey(owner)}_recently_visited`
154+
)
155+
156+
const { data: recentlyVisitedRepoData } = useInfiniteQueryV5(
157+
ReposQueryOpts({
158+
provider,
159+
owner,
160+
repoNames: recentlyVisitedRepoName ? [recentlyVisitedRepoName] : [],
161+
})
162+
)
163+
150164
const isMyOwnerPage = currentUser?.user?.username === owner
151165

152166
const tableData = useMemo(() => {
@@ -168,13 +182,30 @@ const ReposTable = ({
168182
? formatDemoRepos(demoReposData, searchValue)
169183
: []
170184

171-
return [...demoRepos, ...repos]
185+
const filteredRecentlyVisitedRepo = getFilteredRecentlyVisitedRepo(
186+
recentlyVisitedRepoData?.pages[0]?.repos,
187+
searchValue,
188+
owner
189+
)
190+
// only filter out the recently visited repo from the repos list if we are including it
191+
const filteredRepos = filteredRecentlyVisitedRepo
192+
? repos.filter((repo) => recentlyVisitedRepoName !== repo.name)
193+
: repos
194+
195+
return [
196+
...demoRepos,
197+
...(filteredRecentlyVisitedRepo ? [filteredRecentlyVisitedRepo] : []),
198+
...filteredRepos,
199+
]
172200
}, [
173201
reposData?.pages,
174202
demoReposData,
175203
searchValue,
176204
isMyOwnerPage,
177205
mayIncludeDemo,
206+
recentlyVisitedRepoData,
207+
recentlyVisitedRepoName,
208+
owner,
178209
])
179210

180211
useEffect(() => {

src/shared/ListRepo/ReposTable/getReposColumnsHelper.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createColumnHelper } from '@tanstack/react-table'
22

33
import { RepositoryResult } from 'services/repos/ReposQueryOpts'
44
import { formatTimeToNow } from 'shared/utils/dates'
5+
import { transformStringToLocalStorageKey } from 'shared/utils/transformStringToLocalStorageKey'
56
import TotalsNumber from 'ui/TotalsNumber'
67

78
import NoRepoCoverage from './NoRepoCoverage'
@@ -18,6 +19,9 @@ export const getReposColumnsHelper = ({
1819
const columnHelper = createColumnHelper<
1920
RepositoryResult & { isDemo?: boolean }
2021
>()
22+
const recentlyVisitedRepoName = localStorage.getItem(
23+
`${transformStringToLocalStorageKey(owner)}_recently_visited`
24+
)
2125
const nameColumn = columnHelper.accessor('name', {
2226
header: 'Name',
2327
id: 'name',
@@ -39,6 +43,9 @@ export const getReposColumnsHelper = ({
3943
showRepoOwner={!owner}
4044
pageName={pageName}
4145
disabledLink={!isCurrentUserPartOfOrg && !repo?.active}
46+
isRecentlyVisited={
47+
!!recentlyVisitedRepoName && recentlyVisitedRepoName === repo?.name
48+
}
4249
/>
4350
)
4451
},

0 commit comments

Comments
 (0)