Skip to content

Commit 127c328

Browse files
committed
feat: bitbucket cloud support
add bitbucket icon, implement account refresh Signed-off-by: Adam Setch <[email protected]>
1 parent b75f785 commit 127c328

File tree

11 files changed

+162
-46
lines changed

11 files changed

+162
-46
lines changed

src/components/icons/AuthMethodIcon.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const AuthMethodIcon: FC<IAuthMethodIcon> = (props: IAuthMethodIcon) => {
1414
{props.type === 'GitHub App' && <AppsIcon size={props.size} />}
1515
{props.type === 'Personal Access Token' && <KeyIcon size={props.size} />}
1616
{props.type === 'OAuth App' && <PersonIcon size={props.size} />}
17+
{props.type === 'App Password' && <KeyIcon size={props.size} />}
1718
</span>
1819
);
1920
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { FC } from 'react';
2+
import { Size } from '../../types';
3+
import { cn } from '../../utils/cn';
4+
5+
interface IBitbucketIcon {
6+
onClick?: () => void;
7+
size: Size;
8+
}
9+
10+
export const BitbucketIcon: FC<IBitbucketIcon> = ({
11+
onClick,
12+
size = Size.MEDIUM,
13+
...props
14+
}: IBitbucketIcon) => (
15+
<svg
16+
className={cn(
17+
size === Size.XSMALL && 'size-4',
18+
size === Size.SMALL && 'size-6',
19+
size === Size.MEDIUM && 'size-8',
20+
size === Size.LARGE && 'size-10',
21+
)}
22+
onClick={() => onClick?.()}
23+
xmlns="https://www.w3.org/2000/svg"
24+
xmlnsXlink="https://www.w3.org/1999/xlink"
25+
viewBox="0 0 512 512"
26+
role="img"
27+
aria-label="Bitbucket Cloud"
28+
{...props}
29+
>
30+
<rect width="512" height="512" rx="15%" fill="none" fillRule="evenodd" />
31+
<path
32+
fill="#2684ff"
33+
d="M422 130a10 10 0 00-9.9-11.7H100.5a10 10 0 00-10 11.7L136 409a10 10 0 009.9 8.4h221c5 0 9.2-3.5 10 -8.4L422 130zM291 316.8h-69.3l-18.7-98h104.8z"
34+
/>
35+
<path
36+
fill="url(#a)"
37+
d="M59.632 25.2H40.94l-3.1 18.3h-13v18.9H52c1 0 1.7-.7 1.8-1.6l5.8-35.6z"
38+
transform="translate(89.8 85) scale(5.3285)"
39+
/>
40+
<linearGradient
41+
id="a"
42+
x2="1"
43+
gradientTransform="rotate(141 22.239 22.239) scale(31.4)"
44+
gradientUnits="userSpaceOnUse"
45+
>
46+
<stop offset="0" stopColor="#0052cc" />
47+
<stop offset="1" stopColor="#2684ff" />
48+
</linearGradient>
49+
</svg>
50+
);

src/components/icons/PlatformIcon.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { BeakerIcon, MarkGithubIcon, ServerIcon } from '@primer/octicons-react';
1+
import { MarkGithubIcon, ServerIcon } from '@primer/octicons-react';
22
import type { FC } from 'react';
33
import type { Size } from '../../types';
44
import type { PlatformType } from '../../utils/auth/types';
5+
import { BitbucketIcon } from './BitbucketIcon';
56

67
export interface IPlatformIcon {
78
type: PlatformType;
@@ -15,7 +16,7 @@ export const PlatformIcon: FC<IPlatformIcon> = (props: IPlatformIcon) => {
1516
{props.type === 'GitHub Enterprise Server' && (
1617
<ServerIcon size={props.size} />
1718
)}
18-
{props.type === 'Bitbucket Cloud' && <BeakerIcon size={props.size} />}
19+
{props.type === 'Bitbucket Cloud' && <BitbucketIcon size={props.size} />}
1920
</span>
2021
);
2122
};

src/components/notification/Pills.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
CheckboxIcon,
23
CommentIcon,
34
IssueClosedIcon,
45
MilestoneIcon,
@@ -24,6 +25,10 @@ export const Pills: FC<IPills> = ({ notification }: IPills) => {
2425
notification.subject.comments > 1 ? 'comments' : 'comment'
2526
}`;
2627

28+
const tasksPillDescription = `${notification.subject.tasks} ${
29+
notification.subject.tasks > 1 ? 'tasks' : 'task'
30+
}`;
31+
2732
const labelsPillDescription = notification.subject.labels
2833
?.map((label) => `🏷️ ${label}`)
2934
.join('\n');
@@ -87,6 +92,14 @@ export const Pills: FC<IPills> = ({ notification }: IPills) => {
8792
}
8893
/>
8994
)}
95+
{notification.subject?.tasks > 0 && (
96+
<PillButton
97+
title={tasksPillDescription}
98+
metric={notification.subject.tasks}
99+
icon={CheckboxIcon}
100+
color={IconColor.GRAY}
101+
/>
102+
)}
90103
</div>
91104
)
92105
);

src/context/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
251251
auth,
252252
'App Password',
253253
token,
254-
`https://api.bitbucket.org/internal/workspaces/${workspace}` as Hostname,
254+
`https://api.bitbucket.org/internal/workspaces/${workspace}` as Hostname, // TODO - ideally we don't set it like this
255255
username,
256256
);
257257
setAuth(updatedAuth);

src/routes/Accounts.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
BeakerIcon,
32
FeedPersonIcon,
43
KeyIcon,
54
MarkGithubIcon,
@@ -17,6 +16,7 @@ import { useNavigate } from 'react-router-dom';
1716
import { Header } from '../components/Header';
1817
import { AuthMethodIcon } from '../components/icons/AuthMethodIcon';
1918
import { AvatarIcon } from '../components/icons/AvatarIcon';
19+
import { BitbucketIcon } from '../components/icons/BitbucketIcon';
2020
import { PlatformIcon } from '../components/icons/PlatformIcon';
2121
import { AppContext } from '../context/App';
2222
import { BUTTON_CLASS_NAME } from '../styles/gitify';
@@ -112,8 +112,13 @@ export const AccountsRoute: FC = () => {
112112
title="Open Host"
113113
onClick={() => openHost(account.hostname)}
114114
>
115-
<PlatformIcon type={account.platform} size={Size.XSMALL} />
116-
{account.hostname}
115+
<div className="flex flex-col-1">
116+
<PlatformIcon
117+
type={account.platform}
118+
size={Size.XSMALL}
119+
/>
120+
{account.hostname.split('/').pop()}
121+
</div>
117122
</button>
118123
</div>
119124
<div>
@@ -227,11 +232,13 @@ export const AccountsRoute: FC = () => {
227232
title="Login with Bitbucket Cloud"
228233
onClick={loginWithBitbucketCloud}
229234
>
230-
<BeakerIcon
231-
size={Size.XLARGE}
232-
aria-label="Login with Bitbucket Cloud"
233-
/>
234-
<PlusIcon size={Size.SMALL} className="mb-2" />
235+
<div className="flex flex-col-1">
236+
<BitbucketIcon
237+
size={Size.MEDIUM}
238+
aria-label="Login with Bitbucket Cloud"
239+
/>
240+
<PlusIcon size={Size.SMALL} className="mb-2" />
241+
</div>
235242
</button>
236243
</div>
237244
</div>

src/typesGitHub.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ export interface GitifySubject {
268268
reviews?: GitifyPullRequestReview[];
269269
linkedIssues?: string[];
270270
comments?: number;
271+
tasks?: number;
271272
labels?: string[];
272273
milestone?: Milestone;
273274
}

src/utils/api/bitbucket.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { AxiosPromise } from 'axios';
2+
import type { Account, Link } from '../../types';
3+
import type { Notification, UserDetails } from '../../typesGitHub';
4+
import { apiRequestBitbucket } from './request';
5+
/**
6+
* List all notifications for the current user, sorted by most recently updated.
7+
*
8+
* Endpoint documentation: https://docs.github.com/en/rest/activity/notifications#list-notifications-for-the-authenticated-user
9+
*/
10+
// TODO - Correct types
11+
export function listBitbucketWork(
12+
account: Account,
13+
): AxiosPromise<Notification[]> {
14+
const url = `${account.hostname}/overview-view-state?fields=pullRequests.reviewing.id,pullRequests.reviewing.title,pullRequest.reviewing.state,pullRequests.reviewing.author,pullRequests.reviewing.created_on,pullRequests.reviewing.updated_on,pullRequests.reviewing.links,pullRequests.reviewing.task_count,pullRequests.reviewing.comment_count,pullRequests.reviewing.destination.repository.*`;
15+
16+
return apiRequestBitbucket(
17+
url.toString() as Link,
18+
'GET',
19+
account.user.login,
20+
account.token,
21+
);
22+
}
23+
24+
/**
25+
* Get the authenticated user
26+
*
27+
* Endpoint documentation: https://docs.github.com/en/rest/users/users#get-the-authenticated-user
28+
*/
29+
export function getBitbucketUser(account: Account): AxiosPromise<UserDetails> {
30+
const url = 'https://api.bitbucket.org/2.0/user';
31+
32+
return apiRequestBitbucket(
33+
url.toString() as Link,
34+
'GET',
35+
account.user.login,
36+
account.token,
37+
);
38+
}

src/utils/api/client.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import type {
2424
} from '../../typesGitHub';
2525
import { QUERY_SEARCH_DISCUSSIONS } from './graphql/discussions';
2626
import { formatAsGitHubSearchSyntax } from './graphql/utils';
27-
import { apiRequestAuth, apiRequestBitbucket } from './request';
27+
import { apiRequestAuth } from './request';
2828
import { getGitHubAPIBaseUrl, getGitHubGraphQLUrl } from './utils';
2929

3030
/**
@@ -69,25 +69,6 @@ export function listNotificationsForAuthenticatedUser(
6969
return apiRequestAuth(url.toString() as Link, 'GET', account.token);
7070
}
7171

72-
/**
73-
* List all notifications for the current user, sorted by most recently updated.
74-
*
75-
* Endpoint documentation: https://docs.github.com/en/rest/activity/notifications#list-notifications-for-the-authenticated-user
76-
*/
77-
export function listBitbucketWork(
78-
account: Account,
79-
): AxiosPromise<Notification[]> {
80-
const url = `${account.hostname}/overview-view-state?fields=pullRequests.reviewing.id,pullRequests.reviewing.title,pullRequest.reviewing.state,pullRequests.reviewing.author,pullRequests.reviewing.created_on,pullRequests.reviewing.updated_on,pullRequests.reviewing.links,pullRequests.reviewing.task_count,pullRequests.reviewing.comment_count,pullRequests.reviewing.destination.repository.*`;
81-
82-
console.log(url);
83-
return apiRequestBitbucket(
84-
url.toString() as Link,
85-
'GET',
86-
account.user.login,
87-
account.token,
88-
);
89-
}
90-
9172
/**
9273
* Marks a thread as "read." Marking a thread as "read" is equivalent to
9374
* clicking a notification in your notification inbox on GitHub.

src/utils/auth/utils.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
Username,
1414
} from '../../types';
1515
import type { UserDetails } from '../../typesGitHub';
16+
import { getBitbucketUser } from '../api/bitbucket';
1617
import { getAuthenticatedUser } from '../api/client';
1718
import { apiRequest } from '../api/request';
1819
import { Constants } from '../constants';
@@ -138,16 +139,7 @@ export async function addAccount(
138139
token: token,
139140
} as Account;
140141

141-
if (newAccount.platform === 'Bitbucket Cloud') {
142-
newAccount.user = {
143-
id: 0,
144-
login: username,
145-
name: username,
146-
avatar: null,
147-
};
148-
} else {
149-
newAccount = await refreshAccount(newAccount);
150-
}
142+
newAccount = await refreshAccount(newAccount);
151143

152144
return {
153145
accounts: [...auth.accounts, newAccount],
@@ -165,6 +157,38 @@ export function removeAccount(auth: AuthState, account: Account): AuthState {
165157
}
166158

167159
export async function refreshAccount(account: Account): Promise<Account> {
160+
if (account.platform === 'Bitbucket Cloud') {
161+
return refreshBitbucketAccount(account);
162+
}
163+
164+
return refreshGitHubAccount(account);
165+
}
166+
167+
export async function refreshBitbucketAccount(
168+
account: Account,
169+
): Promise<Account> {
170+
try {
171+
// TODO correctly type
172+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
173+
const res: any = await getBitbucketUser(account);
174+
175+
console.log(JSON.stringify(res));
176+
177+
// Refresh user data
178+
account.user = {
179+
id: res.data.account_id,
180+
login: res.data.username,
181+
name: res.data.display_name,
182+
avatar: res.data.links.avatar.href,
183+
};
184+
} catch (error) {
185+
log.error('Failed to refresh account', error);
186+
}
187+
188+
return account;
189+
}
190+
191+
export async function refreshGitHubAccount(account: Account): Promise<Account> {
168192
try {
169193
const res = await getAuthenticatedUser(account.hostname, account.token);
170194

src/utils/notifications.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ import type {
55
SettingsState,
66
} from '../types';
77
import { Notification } from '../typesGitHub';
8-
import {
9-
listBitbucketWork,
10-
listNotificationsForAuthenticatedUser,
11-
} from './api/client';
8+
import { listBitbucketWork } from './api/bitbucket';
9+
import { listNotificationsForAuthenticatedUser } from './api/client';
1210
import { determineFailureType } from './api/errors';
1311
import { getAccountUUID } from './auth/utils';
1412
import { hideWindow, showWindow, updateTrayIcon } from './comms';
@@ -142,7 +140,7 @@ export async function getAllNotifications(
142140
((await accountNotifications.notifications).data as any)
143141
.pullRequests?.reviewing;
144142

145-
// console.log(pulls);
143+
console.log(pulls);
146144
const notifications = pulls?.map((pull: any) => ({
147145
id: `${pull.destination.repository.full_name}-${pull.id}`,
148146
reason: 'review_requested',
@@ -167,6 +165,8 @@ export async function getAllNotifications(
167165
avatar_url: pull.author.links.avatar.href,
168166
type: 'User',
169167
},
168+
comments: pull.comment_count,
169+
tasks: pull.task_count,
170170
},
171171
account: accountNotifications.account,
172172
}));

0 commit comments

Comments
 (0)