Move more code to the core package

This commit is contained in:
Bruno Lemos
2019-04-26 21:38:36 -03:00
parent 3422156e7d
commit 470ebc9ef4
24 changed files with 1050 additions and 940 deletions

View File

@@ -6,6 +6,7 @@ import {
EnhancedGitHubEvent,
getBranchNameFromRef,
getDateSmallText,
getEventIconAndColor,
getEventMetadata,
getFullDateText,
getGitHubAvatarURLFromPayload,
@@ -43,7 +44,6 @@ import {
smallAvatarSize,
smallerTextSize,
} from '../../styles/variables'
import { getEventIconAndColor } from '../../utils/helpers/github/events'
import { tryFocus } from '../../utils/helpers/shared'
import { getCardBackgroundThemeColor } from '../columns/ColumnRenderer'
import { Avatar } from '../common/Avatar'

View File

@@ -7,6 +7,7 @@ import {
getDateSmallText,
getFullDateText,
getGitHubURLForRepo,
getIssueOrPullRequestIconAndColor,
getIssueOrPullRequestNumberFromUrl,
getOwnerAndRepo,
getRepoFullNameFromUrl,
@@ -22,7 +23,6 @@ import {
smallAvatarSize,
smallerTextSize,
} from '../../styles/variables'
import { getIssueOrPullRequestIconAndColor } from '../../utils/helpers/github/issues'
import { tryFocus } from '../../utils/helpers/shared'
import { getCardBackgroundThemeColor } from '../columns/ColumnRenderer'
import { Avatar } from '../common/Avatar'

View File

@@ -10,6 +10,7 @@ import {
getGitHubURLForRepoInvitation,
getGitHubURLForSecurityAlert,
getIssueOrPullRequestNumberFromUrl,
getNotificationIconAndColor,
getOwnerAndRepo,
getUserAvatarByUsername,
GitHubLabel,
@@ -25,7 +26,6 @@ import {
contentPadding,
smallerTextSize,
} from '../../styles/variables'
import { getNotificationIconAndColor } from '../../utils/helpers/github/notifications'
import { fixURL } from '../../utils/helpers/github/url'
import { tryFocus } from '../../utils/helpers/shared'
import { getCardBackgroundThemeColor } from '../columns/ColumnRenderer'

View File

@@ -1,8 +1,7 @@
import React, { ReactElement, ReactNode } from 'react'
import { CardViewMode } from '@devhub/core'
import { CardViewMode, mergeMaxLength } from '@devhub/core'
import { ScrollView, View } from 'react-native'
import { mergeMaxLength } from '../../../../utils/helpers/filters'
import { topCardMargin } from './styles'
export type RenderItem<T> = (params: {

View File

@@ -1,9 +1,11 @@
import React from 'react'
import { GitHubNotificationReason } from '@devhub/core'
import {
getNotificationReasonMetadata,
GitHubNotificationReason,
} from '@devhub/core'
import { Platform } from '../../../../../libs/platform'
import { mutedOpacity, smallerTextSize } from '../../../../../styles/variables'
import { getNotificationReasonMetadata } from '../../../../../utils/helpers/github/notifications'
import { ThemedText } from '../../../../themed/ThemedText'
export interface NotificationReasonProps {

View File

@@ -5,11 +5,19 @@ import { ScrollView, View } from 'react-native'
import {
Column,
eventActions,
eventSubjectTypes,
filterRecordHasAnyForcedValue,
filterRecordWithThisValueCount,
getEventActionMetadata,
getFilterCountMetadata,
getNotificationReasonMetadata,
GitHubEventSubjectType,
GitHubNotificationSubjectType,
isReadFilterChecked,
issueOrPullRequestSubjectTypes,
isUnreadFilterChecked,
notificationReasons,
notificationSubjectTypes,
ThemeColors,
} from '@devhub/core'
import { useAppViewMode } from '../../hooks/use-app-view-mode'
@@ -23,18 +31,6 @@ import {
columnHeaderItemContentSize,
contentPadding,
} from '../../styles/variables'
import {
filterRecordHasAnyForcedValue,
filterRecordWithThisValueCount,
getFilterCountMetadata,
} from '../../utils/helpers/filters'
import { eventSubjectTypes } from '../../utils/helpers/github/events'
import { issueOrPullRequestSubjectTypes } from '../../utils/helpers/github/issues'
import {
getNotificationReasonMetadata,
notificationReasons,
notificationSubjectTypes,
} from '../../utils/helpers/github/notifications'
import { getSubjectTypeMetadata } from '../../utils/helpers/github/shared'
import { CardItemSeparator } from '../cards/partials/CardItemSeparator'
import { Checkbox } from '../common/Checkbox'
@@ -82,10 +78,10 @@ export interface ColumnOptionsProps {
export type ColumnOptionCategory =
| 'event_action'
| 'inbox'
| 'subject_types'
| 'notification_reason'
| 'privacy'
| 'saved_for_later'
| 'subject_types'
| 'unread'
export const ColumnOptions = React.memo((props: ColumnOptionsProps) => {
@@ -102,8 +98,8 @@ export const ColumnOptions = React.memo((props: ColumnOptionsProps) => {
column.type === 'notifications' && 'inbox',
'saved_for_later',
'unread',
column.type === 'activity' && 'event_action',
'subject_types',
column.type === 'activity' && 'event_action',
column.type === 'notifications' && 'notification_reason',
column.type === 'notifications' && 'privacy',
]

View File

@@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'
import { Dimensions, View } from 'react-native'
import {
activityColumnHasAnyFilter,
CardViewMode,
Column as ColumnType,
constants,
@@ -13,6 +14,7 @@ import {
isEventPrivate,
isItemRead,
isNotificationPrivate,
notificationColumnHasAnyFilter,
ThemeColors,
} from '@devhub/core'
import { useAppViewMode } from '../../hooks/use-app-view-mode'
@@ -29,10 +31,6 @@ import {
contentPadding,
sidebarSize,
} from '../../styles/variables'
import {
activityColumnHasAnyFilter,
notificationColumnHasAnyFilter,
} from '../../utils/helpers/filters'
import { FreeTrialHeaderMessage } from '../common/FreeTrialHeaderMessage'
import { separatorSize, separatorThickSize } from '../common/Separator'
import { Spacer } from '../common/Spacer'

View File

@@ -4,13 +4,13 @@ import {
ActivityColumn,
Column,
ColumnSubscription,
filterRecordHasAnyForcedValue,
getEventMetadata,
GitHubEvent,
GraphQLGitHubUser,
guid,
removeUselessURLsFromResponseItem,
} from '@devhub/core'
import { filterRecordHasAnyForcedValue } from '../utils/helpers/filters'
import * as selectors from './selectors'
import { RootState } from './types'

View File

@@ -4,14 +4,14 @@ import _ from 'lodash'
import {
ColumnSubscription,
constants,
mergeEventsPreservingEnhancement,
mergeNotificationsPreservingEnhancement,
normalizeSubscriptions,
removeUselessURLsFromResponseItem,
sortEvents,
sortIssuesOrPullRequests,
sortNotifications,
} from '@devhub/core'
import { mergeEventsPreservingEnhancement } from '../../utils/helpers/github/events'
import { mergeNotificationsPreservingEnhancement } from '../../utils/helpers/github/notifications'
import { Reducer } from '../types'
export interface State {

View File

@@ -12,11 +12,11 @@ import {
isReadFilterChecked,
IssueOrPullRequestColumn,
IssueOrPullRequestColumnSubscription,
itemPassesFilterRecord,
NotificationColumn,
NotificationColumnSubscription,
} from '@devhub/core'
import { emitter } from '../../libs/emitter'
import { itemPassesFilterRecord } from '../../utils/helpers/filters'
import * as actions from '../actions'
import * as selectors from '../selectors'
import { ExtractActionFromActionCreator } from '../types/base'

View File

@@ -35,6 +35,9 @@ import {
GitHubIssueOrPullRequest,
GitHubNotification,
IssueOrPullRequestColumnSubscription,
mergeEventsPreservingEnhancement,
mergeIssuesOrPullRequestsPreservingEnhancement,
mergeNotificationsPreservingEnhancement,
} from '@devhub/core'
import { bugsnag } from '../../libs/bugsnag'
@@ -44,9 +47,6 @@ import {
getNotifications,
octokit,
} from '../../libs/github'
import { mergeEventsPreservingEnhancement } from '../../utils/helpers/github/events'
import { mergeIssuesOrPullRequestsPreservingEnhancement } from '../../utils/helpers/github/issues'
import { mergeNotificationsPreservingEnhancement } from '../../utils/helpers/github/notifications'
import * as actions from '../actions'
import * as selectors from '../selectors'
import { ExtractActionFromActionCreator } from '../types/base'

View File

@@ -6,15 +6,13 @@ import {
EnhancedGitHubEvent,
EnhancedGitHubIssueOrPullRequest,
EnhancedGitHubNotification,
getFilteredEvents,
getFilteredIssueOrPullRequests,
getFilteredNotifications,
sortEvents,
sortIssuesOrPullRequests,
sortNotifications,
} from '@devhub/core'
import {
getFilteredEvents,
getFilteredIssueOrPullRequests,
getFilteredNotifications,
} from '../../utils/helpers/filters'
import { RootState } from '../types'
import { createArraySelector } from './helpers'

View File

@@ -1,355 +0,0 @@
import _ from 'lodash'
import {
ActivityColumnFilters,
BaseColumnFilters,
EnhancedGitHubEvent,
EnhancedGitHubIssueOrPullRequest,
EnhancedGitHubNotification,
getEventMetadata,
getIssueOrPullRequestSubjectType,
isEventPrivate,
isItemRead,
isNotificationPrivate,
IssueOrPullRequestColumnFilters,
mergeSimilarEvents,
NotificationColumnFilters,
sortEvents,
sortIssuesOrPullRequests,
sortNotifications,
} from '@devhub/core'
import { getEventSubjectType } from './github/events'
import { getNotificationSubjectType } from './github/notifications'
export const filterRecordHasAnyForcedValue = (
filtersRecord: Record<string, boolean | undefined> | undefined,
) => {
if (!filtersRecord) return false
return Object.values(filtersRecord).some(value => typeof value === 'boolean')
}
export const filterRecordWithThisValueCount = (
filtersRecord: Record<string, boolean | undefined> | undefined,
valueToCheck: boolean,
): number => {
if (!filtersRecord) return 0
return Object.values(filtersRecord).reduce(
(total, item) => total + (item === valueToCheck ? 1 : 0),
0,
)
}
export function itemPassesFilterRecord<
F extends Record<string, boolean | undefined>
>(filtersRecord: F, value: keyof F, defaultValue: boolean) {
if (!(filtersRecord && value)) return defaultValue
const hasForcedFilter = filterRecordHasAnyForcedValue(filtersRecord)
if (!hasForcedFilter) return defaultValue
const isFilterStrict =
hasForcedFilter &&
filterRecordWithThisValueCount(filtersRecord, defaultValue)
return filtersRecord[value] === !defaultValue ||
(filtersRecord[value] !== defaultValue && isFilterStrict)
? !defaultValue
: defaultValue
}
export function getFilterCountMetadata(
filtersRecord: Record<string, boolean | undefined> | undefined,
totalCount: number,
defaultValue: boolean,
): { checked: number; unchecked: number; total: number } {
if (!filtersRecord) return { checked: 0, unchecked: 0, total: totalCount }
const keys = Object.keys(filtersRecord)
const hasForcedFilter = filterRecordHasAnyForcedValue(filtersRecord)
if (!hasForcedFilter) {
return {
checked: defaultValue ? totalCount : 0,
unchecked: !defaultValue ? totalCount : 0,
total: totalCount,
}
}
const isFilterStrict =
hasForcedFilter &&
filterRecordWithThisValueCount(filtersRecord, defaultValue)
if (isFilterStrict) {
return keys.reduce(
(result, key) => {
const checked = filtersRecord[key] === defaultValue
return {
...result,
checked: checked ? result.checked + 1 : result.checked,
unchecked: !checked ? result.unchecked + 1 : result.unchecked,
}
},
{ checked: 0, unchecked: 0, total: totalCount },
)
}
return keys.reduce(
(result, key) => {
const checked =
filtersRecord[key] === !defaultValue ? !defaultValue : defaultValue
return {
...result,
checked: checked ? result.checked : result.checked - 1,
unchecked: !checked ? result.unchecked : result.unchecked - 1,
}
},
{ checked: totalCount, unchecked: totalCount, total: totalCount },
)
}
function baseColumnHasAnyFilter(filters: BaseColumnFilters | undefined) {
if (!filters) return false
if (filters.clearedAt) return true
if (typeof filters.private === 'boolean') return true
if (typeof filters.saved === 'boolean') return true
if (
filters.subjectTypes &&
filterRecordHasAnyForcedValue(filters.subjectTypes)
) {
return true
}
if (typeof filters.unread === 'boolean') return true
return false
}
export function activityColumnHasAnyFilter(
filters: ActivityColumnFilters | undefined,
) {
if (!filters) return false
if (baseColumnHasAnyFilter(filters)) return true
if (
filters.activity &&
filterRecordHasAnyForcedValue(filters.activity.actions)
) {
return true
}
return false
}
export function issueOrPullRequestColumnHasAnyFilter(
filters: IssueOrPullRequestColumnFilters | undefined,
) {
if (!filters) return false
if (baseColumnHasAnyFilter(filters)) return true
return false
}
export function notificationColumnHasAnyFilter(
filters: NotificationColumnFilters | undefined,
) {
if (!filters) return false
if (baseColumnHasAnyFilter(filters)) return true
if (filters.notifications && filters.notifications.participating) {
return true
}
if (
filters.notifications &&
filterRecordHasAnyForcedValue(filters.notifications.reasons)
) {
return true
}
return false
}
export function getFilteredIssueOrPullRequests(
items: EnhancedGitHubIssueOrPullRequest[],
filters: IssueOrPullRequestColumnFilters | undefined,
) {
let _items = sortIssuesOrPullRequests(items)
if (filters && issueOrPullRequestColumnHasAnyFilter(filters)) {
_items = _items.filter(item => {
if (
!itemPassesFilterRecord(
filters.subjectTypes!,
getIssueOrPullRequestSubjectType(item)!,
true,
)
)
return false
if (
typeof filters.unread === 'boolean' &&
filters.unread !== !isItemRead(item)
) {
return false
}
const showSaveForLater = filters.saved !== false
const showInbox = filters.saved !== true
const showCleared = false
if (
filters.clearedAt &&
(!item.updated_at || item.updated_at <= filters.clearedAt)
)
if (!(showSaveForLater && item.saved))
/* && isItemRead(notification) */
return showCleared
if (item.saved) return showSaveForLater
return showInbox
})
}
return _items
}
export function getFilteredNotifications(
notifications: EnhancedGitHubNotification[],
filters: NotificationColumnFilters | undefined,
) {
let _notifications = sortNotifications(notifications)
const reasonsFilter =
filters && filters.notifications && filters.notifications.reasons
if (filters && notificationColumnHasAnyFilter(filters)) {
_notifications = _notifications.filter(notification => {
if (!itemPassesFilterRecord(reasonsFilter!, notification.reason, true))
return false
if (
filters.notifications &&
filters.notifications.participating &&
notification.reason === 'subscribed'
)
return false
if (
!itemPassesFilterRecord(
filters.subjectTypes!,
getNotificationSubjectType(notification)!,
true,
)
)
return false
if (
typeof filters.unread === 'boolean' &&
filters.unread !== !isItemRead(notification)
) {
return false
}
if (
typeof filters.private === 'boolean' &&
isNotificationPrivate(notification) !== filters.private
) {
return false
}
const showSaveForLater = filters.saved !== false
const showInbox = filters.saved !== true
const showCleared = false
if (
filters.clearedAt &&
(!notification.updated_at ||
notification.updated_at <= filters.clearedAt)
)
if (!(showSaveForLater && notification.saved))
/* && isItemRead(notification) */
return showCleared
if (notification.saved) return showSaveForLater
return showInbox
})
}
return _notifications
}
export function getFilteredEvents(
events: EnhancedGitHubEvent[],
filters: ActivityColumnFilters | undefined,
mergeSimilar: boolean,
) {
let _events = sortEvents(events)
const actionFilter = filters && filters.activity && filters.activity.actions
if (filters && activityColumnHasAnyFilter(filters)) {
_events = _events.filter(event => {
if (
!itemPassesFilterRecord(
actionFilter!,
getEventMetadata(event).action!,
true,
)
)
return false
if (
!itemPassesFilterRecord(
filters.subjectTypes!,
getEventSubjectType(event)!,
true,
)
)
return false
if (
typeof filters.unread === 'boolean' &&
filters.unread !== !isItemRead(event)
) {
return false
}
if (
typeof filters.private === 'boolean' &&
isEventPrivate(event) !== filters.private
) {
return false
}
const showSaveForLater = filters.saved !== false
const showInbox = filters.saved !== true
const showCleared = false
if (
filters.clearedAt &&
(!event.created_at || event.created_at <= filters.clearedAt)
)
if (!(showSaveForLater && event.saved))
/* && isItemRead(event) */
return showCleared
if (event.saved) return showSaveForLater
return showInbox
})
}
return mergeSimilar ? mergeSimilarEvents(_events, mergeMaxLength) : _events
}
export const mergeMaxLength = 5

View File

@@ -1,248 +0,0 @@
import _ from 'lodash'
import {
EnhancedGitHubEvent,
GitHubEventSubjectType,
GitHubIcon,
GitHubIssue,
GitHubPullRequest,
isPullRequest,
isTagMainEvent,
sortEvents,
ThemeColors,
} from '@devhub/core'
import { bugsnag } from '../../../libs/bugsnag'
import {
getCommitIconAndColor,
getIssueIconAndColor,
getPullRequestIconAndColor,
getReleaseIconAndColor,
getTagIconAndColor,
} from './shared'
export const eventSubjectTypes: GitHubEventSubjectType[] = [
'Branch',
'Commit',
'Issue',
'PullRequest',
'PullRequestReview',
'Release',
'Repository',
'Tag',
'User',
'Wiki',
]
export function getEventSubjectType(
event: EnhancedGitHubEvent,
): GitHubEventSubjectType | null {
if (!(event && event.type)) return null
switch (event.type) {
case 'CommitCommentEvent':
return 'Commit'
case 'CreateEvent':
case 'DeleteEvent': {
switch (event.payload.ref_type) {
case 'repository':
return 'Repository'
case 'branch':
return 'Branch'
case 'tag':
return 'Tag'
default:
return null
}
}
case 'ForkEvent':
return 'Repository'
case 'GollumEvent':
return 'Wiki'
case 'IssueCommentEvent':
return 'Issue'
case 'IssuesEvent':
return 'Issue'
case 'MemberEvent':
return 'User'
case 'PublicEvent':
return 'Repository'
case 'PullRequestEvent':
return 'PullRequest'
case 'PullRequestReviewCommentEvent':
return 'PullRequestReview'
case 'PullRequestReviewEvent':
return 'PullRequestReview'
case 'PushEvent':
return 'Commit'
case 'ReleaseEvent':
return 'Release'
case 'WatchEvent':
case 'WatchEvent:OneUserMultipleRepos':
return 'Repository'
default:
return null
}
}
export function getEventIconAndColor(
event: EnhancedGitHubEvent,
): { color?: keyof ThemeColors; icon: GitHubIcon; subIcon?: GitHubIcon } {
switch (event.type) {
case 'CommitCommentEvent':
return {
...getCommitIconAndColor(),
subIcon: 'comment-discussion',
}
case 'CreateEvent': {
switch (event.payload.ref_type) {
case 'repository':
return { icon: 'repo' }
case 'branch':
return { icon: 'git-branch' }
case 'tag':
return { icon: 'tag' }
default:
return { icon: 'plus' }
}
}
case 'DeleteEvent': {
switch (event.payload.ref_type) {
case 'repository':
return { icon: 'repo', color: 'red' }
case 'branch':
return { icon: 'git-branch', color: 'red' }
case 'tag':
return { icon: 'tag', color: 'red' }
default:
return { icon: 'trashcan' }
}
}
case 'ForkEvent':
return { icon: 'repo-forked' }
case 'GollumEvent':
return { icon: 'book' }
case 'IssueCommentEvent': {
return {
...(isPullRequest(event.payload.issue)
? getPullRequestIconAndColor(event.payload.issue as GitHubPullRequest)
: getIssueIconAndColor(event.payload.issue)),
subIcon: 'comment-discussion',
}
}
case 'IssuesEvent': {
const issue = event.payload.issue
switch (event.payload.action) {
case 'opened':
return getIssueIconAndColor({ state: 'open' } as GitHubIssue)
case 'closed':
return getIssueIconAndColor({ state: 'closed' } as GitHubIssue)
case 'reopened':
return {
...getIssueIconAndColor({ state: 'open' } as GitHubIssue),
icon: 'issue-reopened',
}
// case 'assigned':
// case 'unassigned':
// case 'labeled':
// case 'unlabeled':
// case 'edited':
// case 'milestoned':
// case 'demilestoned':
default:
return getIssueIconAndColor(issue)
}
}
case 'MemberEvent':
return { icon: 'person' }
case 'PublicEvent':
return { icon: 'globe', color: 'blue' }
case 'PullRequestEvent': {
const pullRequest = event.payload.pull_request
switch (event.payload.action) {
case 'opened':
case 'reopened':
return getPullRequestIconAndColor({
draft: pullRequest.draft,
state: 'open',
merged: false,
merged_at: undefined,
mergeable_state: pullRequest.mergeable_state,
})
// case 'closed': return getPullRequestIconAndColor({ state: 'closed' } as GitHubPullRequest);
// case 'assigned':
// case 'unassigned':
// case 'labeled':
// case 'unlabeled':
// case 'edited':
default:
return getPullRequestIconAndColor(pullRequest)
}
}
case 'PullRequestReviewCommentEvent':
case 'PullRequestReviewEvent': {
return {
...getPullRequestIconAndColor(event.payload.pull_request),
subIcon: 'comment-discussion',
}
}
case 'PushEvent':
return { icon: 'code' }
case 'ReleaseEvent':
return isTagMainEvent(event)
? getTagIconAndColor()
: getReleaseIconAndColor()
case 'WatchEvent':
case 'WatchEvent:OneUserMultipleRepos':
return { icon: 'star', color: 'yellow' }
default: {
const message = `Unknown event type: ${(event as any).type}`
bugsnag.notify(new Error(message))
console.error(message)
return { icon: 'mark-github' }
}
}
}
export function mergeEventsPreservingEnhancement(
newItems: EnhancedGitHubEvent[],
prevItems: EnhancedGitHubEvent[],
) {
return sortEvents(
_.uniqBy(_.concat(newItems, prevItems), 'id').map(item => {
const newItem = newItems.find(i => i.id === item.id)
const existingItem = prevItems.find(i => i.id === item.id)
if (!(newItem && existingItem)) return item
const mergedItem = {
forceUnreadLocally: existingItem.forceUnreadLocally,
last_read_at: existingItem.last_read_at,
last_unread_at: existingItem.last_unread_at,
saved: existingItem.saved,
unread: existingItem.unread,
...newItem,
}
return _.isEqual(mergedItem, existingItem) ? existingItem : mergedItem
}),
)
}

View File

@@ -1,49 +0,0 @@
import _ from 'lodash'
import {
EnhancedGitHubIssueOrPullRequest,
GitHubIssue,
GitHubIssueOrPullRequest,
GitHubIssueOrPullRequestSubjectType,
GitHubPullRequest,
sortIssuesOrPullRequests,
} from '@devhub/core'
import { getIssueIconAndColor, getPullRequestIconAndColor } from './shared'
export const issueOrPullRequestSubjectTypes: GitHubIssueOrPullRequestSubjectType[] = [
'Issue',
'PullRequest',
]
export function getIssueOrPullRequestIconAndColor(
type: GitHubIssueOrPullRequestSubjectType,
issueOrPullRequest: GitHubIssueOrPullRequest,
) {
return type === 'PullRequest'
? getPullRequestIconAndColor(issueOrPullRequest as GitHubPullRequest)
: getIssueIconAndColor(issueOrPullRequest as GitHubIssue)
}
export function mergeIssuesOrPullRequestsPreservingEnhancement(
newItems: EnhancedGitHubIssueOrPullRequest[],
prevItems: EnhancedGitHubIssueOrPullRequest[],
) {
return sortIssuesOrPullRequests(
_.uniqBy(_.concat(newItems || [], prevItems || []), 'id').map(item => {
const newItem = (newItems || []).find(i => i.id === item.id)
const existingItem = prevItems.find(i => i.id === item.id)
if (!(newItem && existingItem)) return item
const mergedItem = {
forceUnreadLocally: existingItem.forceUnreadLocally,
last_read_at: existingItem.last_read_at,
last_unread_at: existingItem.last_unread_at,
saved: existingItem.saved,
unread: existingItem.unread,
...newItem,
}
return _.isEqual(mergedItem, existingItem) ? existingItem : mergedItem
}),
)
}

View File

@@ -1,238 +0,0 @@
import _ from 'lodash'
import {
capitalize,
EnhancedGitHubNotification,
GitHubIcon,
GitHubIssue,
GitHubIssueOrPullRequest,
GitHubNotification,
GitHubNotificationReason,
GitHubNotificationSubjectType,
GitHubPullRequest,
sortNotifications,
ThemeColors,
} from '@devhub/core'
import { bugsnag } from '../../../libs/bugsnag'
import {
getCommitIconAndColor,
getIssueIconAndColor,
getPullRequestIconAndColor,
getReleaseIconAndColor,
} from './shared'
export const notificationReasons: GitHubNotificationReason[] = [
'assign',
'author',
'comment',
'invitation',
'manual',
'mention',
'review_requested',
'security_alert',
'state_change',
'subscribed',
'team_mention',
]
export const notificationSubjectTypes: GitHubNotificationSubjectType[] = [
'Commit',
'Issue',
'PullRequest',
'Release',
'RepositoryInvitation',
'RepositoryVulnerabilityAlert',
]
export function getNotificationSubjectType(
notification: GitHubNotification,
): GitHubNotificationSubjectType | null {
if (!(notification && notification.subject && notification.subject.type))
return null
return notification.subject.type
}
export function getNotificationIconAndColor(
notification: GitHubNotification,
payload: GitHubIssueOrPullRequest | undefined,
): { icon: GitHubIcon; color?: keyof ThemeColors; tooltip: string } {
const { subject } = notification
const { type } = subject
switch (type) {
case 'Commit':
return getCommitIconAndColor()
case 'Issue':
return getIssueIconAndColor(payload as GitHubIssue)
case 'PullRequest':
return getPullRequestIconAndColor(payload as GitHubPullRequest)
case 'Release':
return getReleaseIconAndColor()
case 'RepositoryInvitation':
return {
icon: 'mail',
color: 'brown',
tooltip: 'Repository invitation',
}
case 'RepositoryVulnerabilityAlert':
return {
icon: 'alert',
color: 'yellow',
tooltip: 'Repository vulnerability alert',
}
default: {
const message = `Unknown event type: ${(event as any).type}`
bugsnag.notify(new Error(message))
console.error(message)
return { icon: 'bell', tooltip: '' }
}
}
}
export function getNotificationReasonMetadata<
T extends GitHubNotificationReason
>(
reason: T,
): {
color: keyof ThemeColors
reason: T
label: string
fullDescription: string
// smallDescription: string
} {
switch (reason) {
case 'assign':
return {
reason,
color: 'pink',
fullDescription: 'You were assigned to the thread',
// smallDescription: 'You were assigned',
label: 'Assigned',
}
case 'author':
return {
reason,
color: 'lightRed',
fullDescription: 'You created the thread',
// smallDescription: 'You created',
label: 'Author',
}
case 'comment':
return {
reason,
color: 'blue',
fullDescription: 'You commented on the thread',
// smallDescription: 'You commented',
label: 'Commented',
}
case 'invitation':
return {
reason,
color: 'brown',
fullDescription:
'You accepted an invitation to contribute to the repository',
// smallDescription: 'You were invited',
label: 'Invited',
}
case 'manual':
return {
reason,
color: 'teal',
fullDescription: 'You manually subscribed to the thread',
// smallDescription: 'You subscribed manually',
label: 'Manual',
}
case 'mention':
return {
reason,
color: 'orange',
fullDescription: 'You were @mentioned in the thread',
// smallDescription: 'You were mentioned',
label: 'Mentioned',
}
case 'state_change':
return {
reason,
color: 'purple',
fullDescription: 'You opened or closed the issue/pr',
// smallDescription: 'You opened/closed',
label: 'State changed',
}
case 'subscribed':
return {
reason,
color: 'blueGray',
fullDescription: "You're watching the repository",
// smallDescription: 'You are watching',
label: 'Watching',
}
case 'team_mention':
return {
reason,
color: 'yellow',
fullDescription: 'Your team was mentioned in the thread',
// smallDescription: 'Team mentioned',
label: 'Team mentioned',
}
case 'review_requested':
return {
reason,
color: 'yellow',
fullDescription: 'Someone requested your review in the pull request',
// smallDescription: 'Review requested',
label: 'Review requested',
}
case 'security_alert':
return {
reason,
color: 'red',
fullDescription: 'Potential security vulnerability found',
// smallDescription: 'Security alert',
label: 'Security',
}
default:
return {
reason,
color: 'gray',
fullDescription: '',
// smallDescription: '',
label: capitalize(reason),
}
}
}
export function mergeNotificationsPreservingEnhancement(
newItems: EnhancedGitHubNotification[],
prevItems: EnhancedGitHubNotification[],
) {
return sortNotifications(
_.uniqBy(_.concat(newItems, prevItems), 'id').map(item => {
const newItem = newItems.find(i => i.id === item.id)
const existingItem = prevItems.find(i => i.id === item.id)
if (!(newItem && existingItem)) return item
const mergedItem = {
forceUnreadLocally: existingItem.forceUnreadLocally,
last_read_at: existingItem.last_read_at,
last_unread_at: existingItem.last_unread_at,
saved: existingItem.saved,
unread: existingItem.unread,
...newItem,
}
return _.isEqual(mergedItem, existingItem) ? existingItem : mergedItem
}),
)
}

View File

@@ -122,7 +122,9 @@ export function getSubjectTypeMetadata<
label: string
subjectType: T
} {
switch (subjectType) {
switch (
subjectType as GitHubEventSubjectType | GitHubNotificationSubjectType
) {
case 'PullRequestReview': {
return {
label: 'Review',

View File

@@ -0,0 +1,343 @@
import _ from 'lodash'
import {
ActivityColumnFilters,
BaseColumnFilters,
EnhancedGitHubEvent,
EnhancedGitHubIssueOrPullRequest,
EnhancedGitHubNotification,
IssueOrPullRequestColumnFilters,
NotificationColumnFilters,
} from '../types'
import {
getIssueOrPullRequestSubjectType,
getNotificationSubjectType,
isItemRead,
sortIssuesOrPullRequests,
sortNotifications,
} from './github'
import {
getEventMetadata,
getEventSubjectType,
mergeSimilarEvents,
sortEvents,
} from './github/events'
import { isEventPrivate, isNotificationPrivate } from './shared'
export const filterRecordHasAnyForcedValue = (
filtersRecord: Record<string, boolean | undefined> | undefined,
) => {
if (!filtersRecord) return false
return Object.values(filtersRecord).some(value => typeof value === 'boolean')
}
export const filterRecordWithThisValueCount = (
filtersRecord: Record<string, boolean | undefined> | undefined,
valueToCheck: boolean,
): number => {
if (!filtersRecord) return 0
return Object.values(filtersRecord).reduce(
(total, item) => total + (item === valueToCheck ? 1 : 0),
0,
)
}
export function itemPassesFilterRecord<
F extends Record<string, boolean | undefined>
>(filtersRecord: F, value: keyof F, defaultValue: boolean) {
if (!(filtersRecord && value)) return defaultValue
const hasForcedFilter = filterRecordHasAnyForcedValue(filtersRecord)
if (!hasForcedFilter) return defaultValue
const isFilterStrict =
hasForcedFilter &&
filterRecordWithThisValueCount(filtersRecord, defaultValue)
return filtersRecord[value] === !defaultValue ||
(filtersRecord[value] !== defaultValue && isFilterStrict)
? !defaultValue
: defaultValue
}
export function getFilterCountMetadata(
filtersRecord: Record<string, boolean | undefined> | undefined,
totalCount: number,
defaultValue: boolean,
): { checked: number; unchecked: number; total: number } {
if (!filtersRecord) return { checked: 0, unchecked: 0, total: totalCount }
const keys = Object.keys(filtersRecord)
const hasForcedFilter = filterRecordHasAnyForcedValue(filtersRecord)
if (!hasForcedFilter) {
return {
checked: defaultValue ? totalCount : 0,
unchecked: !defaultValue ? totalCount : 0,
total: totalCount,
}
}
const isFilterStrict =
hasForcedFilter &&
filterRecordWithThisValueCount(filtersRecord, defaultValue)
if (isFilterStrict) {
return keys.reduce(
(result, key) => {
const checked = filtersRecord[key] === defaultValue
return {
...result,
checked: checked ? result.checked + 1 : result.checked,
unchecked: !checked ? result.unchecked + 1 : result.unchecked,
}
},
{ checked: 0, unchecked: 0, total: totalCount },
)
}
return keys.reduce(
(result, key) => {
const checked =
filtersRecord[key] === !defaultValue ? !defaultValue : defaultValue
return {
...result,
checked: checked ? result.checked : result.checked - 1,
unchecked: !checked ? result.unchecked : result.unchecked - 1,
}
},
{ checked: totalCount, unchecked: totalCount, total: totalCount },
)
}
function baseColumnHasAnyFilter(filters: BaseColumnFilters | undefined) {
if (!filters) return false
if (filters.clearedAt) return true
if (typeof filters.private === 'boolean') return true
if (typeof filters.saved === 'boolean') return true
if (typeof filters.unread === 'boolean') return true
if (
filters.subjectTypes &&
filterRecordHasAnyForcedValue(filters.subjectTypes)
) {
return true
}
return false
}
export function activityColumnHasAnyFilter(
filters: ActivityColumnFilters | undefined,
) {
if (!filters) return false
if (baseColumnHasAnyFilter(filters)) return true
if (
filters.activity &&
filterRecordHasAnyForcedValue(filters.activity.actions)
) {
return true
}
return false
}
export function issueOrPullRequestColumnHasAnyFilter(
filters: IssueOrPullRequestColumnFilters | undefined,
) {
if (!filters) return false
if (baseColumnHasAnyFilter(filters)) return true
return false
}
export function notificationColumnHasAnyFilter(
filters: NotificationColumnFilters | undefined,
) {
if (!filters) return false
if (baseColumnHasAnyFilter(filters)) return true
if (filters.notifications && filters.notifications.participating) {
return true
}
if (
filters.notifications &&
filterRecordHasAnyForcedValue(filters.notifications.reasons)
) {
return true
}
return false
}
export function getFilteredIssueOrPullRequests(
items: EnhancedGitHubIssueOrPullRequest[],
filters: IssueOrPullRequestColumnFilters | undefined,
) {
let _items = sortIssuesOrPullRequests(items)
if (filters && issueOrPullRequestColumnHasAnyFilter(filters)) {
_items = _items.filter(item => {
const subjectType = getIssueOrPullRequestSubjectType(item)
if (!itemPassesFilterRecord(filters.subjectTypes!, subjectType!, true))
return false
if (
typeof filters.unread === 'boolean' &&
filters.unread !== !isItemRead(item)
) {
return false
}
const showSaveForLater = filters.saved !== false
const showInbox = filters.saved !== true
const showCleared = false
if (
filters.clearedAt &&
(!item.updated_at || item.updated_at <= filters.clearedAt)
)
if (!(showSaveForLater && item.saved))
/* && isItemRead(notification) */
return showCleared
if (item.saved) return showSaveForLater
return showInbox
})
}
return _items
}
export function getFilteredNotifications(
notifications: EnhancedGitHubNotification[],
filters: NotificationColumnFilters | undefined,
) {
let _notifications = sortNotifications(notifications)
const reasonsFilter =
filters && filters.notifications && filters.notifications.reasons
if (filters && notificationColumnHasAnyFilter(filters)) {
_notifications = _notifications.filter(item => {
const subjectType = getNotificationSubjectType(item)
if (!itemPassesFilterRecord(reasonsFilter!, item.reason, true))
return false
if (
filters.notifications &&
filters.notifications.participating &&
item.reason === 'subscribed'
)
return false
if (!itemPassesFilterRecord(filters.subjectTypes!, subjectType!, true))
return false
if (
typeof filters.unread === 'boolean' &&
filters.unread !== !isItemRead(item)
) {
return false
}
if (
typeof filters.private === 'boolean' &&
isNotificationPrivate(item) !== filters.private
) {
return false
}
const showSaveForLater = filters.saved !== false
const showInbox = filters.saved !== true
const showCleared = false
if (
filters.clearedAt &&
(!item.updated_at || item.updated_at <= filters.clearedAt)
)
if (!(showSaveForLater && item.saved))
/* && isItemRead(notification) */
return showCleared
if (item.saved) return showSaveForLater
return showInbox
})
}
return _notifications
}
export function getFilteredEvents(
events: EnhancedGitHubEvent[],
filters: ActivityColumnFilters | undefined,
mergeSimilar: boolean,
) {
let _events = sortEvents(events)
const actionFilter = filters && filters.activity && filters.activity.actions
if (filters && activityColumnHasAnyFilter(filters)) {
_events = _events.filter(item => {
const subjectType = getEventSubjectType(item)
if (!itemPassesFilterRecord(filters.subjectTypes!, subjectType!, true))
return false
if (
!itemPassesFilterRecord(
actionFilter!,
getEventMetadata(item).action!,
true,
)
)
return false
if (
typeof filters.unread === 'boolean' &&
filters.unread !== !isItemRead(item)
) {
return false
}
if (
typeof filters.private === 'boolean' &&
isEventPrivate(item) !== filters.private
) {
return false
}
const showSaveForLater = filters.saved !== false
const showInbox = filters.saved !== true
const showCleared = false
if (
filters.clearedAt &&
(!item.created_at || item.created_at <= filters.clearedAt)
)
if (!(showSaveForLater && item.saved))
/* && isItemRead(event) */
return showCleared
if (item.saved) return showSaveForLater
return showInbox
})
}
return mergeSimilar ? mergeSimilarEvents(_events, mergeMaxLength) : _events
}
export const mergeMaxLength = 5

View File

@@ -5,18 +5,22 @@ import {
EnhancedGitHubEvent,
GitHubEventAction,
GitHubEventSubjectType,
GitHubIcon,
GitHubIssue,
GitHubPullRequest,
MultipleStarEvent,
ThemeColors,
} from '../../types'
import { getBranchNameFromRef, isDraft, isPullRequest } from './shared'
export function getOlderEventDate(
events: EnhancedGitHubEvent[],
field: keyof EnhancedGitHubEvent = 'created_at',
) {
const olderItem = sortEvents(events, field, 'desc').pop()
return olderItem && olderItem[field]
}
import {
getBranchNameFromRef,
getCommitIconAndColor,
getIssueIconAndColor,
getPullRequestIconAndColor,
getReleaseIconAndColor,
getTagIconAndColor,
isDraft,
isPullRequest,
} from './shared'
export const eventActions: GitHubEventAction[] = [
'added',
@@ -35,6 +39,27 @@ export const eventActions: GitHubEventAction[] = [
'updated',
]
export const eventSubjectTypes: GitHubEventSubjectType[] = [
'Branch',
'Commit',
'Issue',
'PullRequest',
'PullRequestReview',
'Release',
'Repository',
'Tag',
'User',
'Wiki',
]
export function getOlderEventDate(
events: EnhancedGitHubEvent[],
field: keyof EnhancedGitHubEvent = 'created_at',
) {
const olderItem = sortEvents(events, field, 'desc').pop()
return olderItem && olderItem[field]
}
export function getEventActionMetadata<T extends GitHubEventAction>(
action: T,
): { label: string; action: T } {
@@ -635,3 +660,216 @@ export function sortEvents(
.orderBy(field, order)
.value()
}
export function getEventSubjectType(
event: EnhancedGitHubEvent,
): GitHubEventSubjectType | null {
if (!(event && event.type)) return null
switch (event.type) {
case 'CommitCommentEvent':
return 'Commit'
case 'CreateEvent':
case 'DeleteEvent': {
switch (event.payload.ref_type) {
case 'repository':
return 'Repository'
case 'branch':
return 'Branch'
case 'tag':
return 'Tag'
default:
return null
}
}
case 'ForkEvent':
return 'Repository'
case 'GollumEvent':
return 'Wiki'
case 'IssueCommentEvent':
return 'Issue'
case 'IssuesEvent':
return 'Issue'
case 'MemberEvent':
return 'User'
case 'PublicEvent':
return 'Repository'
case 'PullRequestEvent':
return 'PullRequest'
case 'PullRequestReviewCommentEvent':
return 'PullRequestReview'
case 'PullRequestReviewEvent':
return 'PullRequestReview'
case 'PushEvent':
return 'Commit'
case 'ReleaseEvent':
return 'Release'
case 'WatchEvent':
case 'WatchEvent:OneUserMultipleRepos':
return 'Repository'
default:
return null
}
}
export function getEventIconAndColor(
event: EnhancedGitHubEvent,
): { color?: keyof ThemeColors; icon: GitHubIcon; subIcon?: GitHubIcon } {
switch (event.type) {
case 'CommitCommentEvent':
return {
...getCommitIconAndColor(),
subIcon: 'comment-discussion',
}
case 'CreateEvent': {
switch (event.payload.ref_type) {
case 'repository':
return { icon: 'repo' }
case 'branch':
return { icon: 'git-branch' }
case 'tag':
return { icon: 'tag' }
default:
return { icon: 'plus' }
}
}
case 'DeleteEvent': {
switch (event.payload.ref_type) {
case 'repository':
return { icon: 'repo', color: 'red' }
case 'branch':
return { icon: 'git-branch', color: 'red' }
case 'tag':
return { icon: 'tag', color: 'red' }
default:
return { icon: 'trashcan' }
}
}
case 'ForkEvent':
return { icon: 'repo-forked' }
case 'GollumEvent':
return { icon: 'book' }
case 'IssueCommentEvent': {
return {
...(isPullRequest(event.payload.issue)
? getPullRequestIconAndColor(event.payload.issue as GitHubPullRequest)
: getIssueIconAndColor(event.payload.issue)),
subIcon: 'comment-discussion',
}
}
case 'IssuesEvent': {
const issue = event.payload.issue
switch (event.payload.action) {
case 'opened':
return getIssueIconAndColor({ state: 'open' } as GitHubIssue)
case 'closed':
return getIssueIconAndColor({ state: 'closed' } as GitHubIssue)
case 'reopened':
return {
...getIssueIconAndColor({ state: 'open' } as GitHubIssue),
icon: 'issue-reopened',
}
// case 'assigned':
// case 'unassigned':
// case 'labeled':
// case 'unlabeled':
// case 'edited':
// case 'milestoned':
// case 'demilestoned':
default:
return getIssueIconAndColor(issue)
}
}
case 'MemberEvent':
return { icon: 'person' }
case 'PublicEvent':
return { icon: 'globe', color: 'blue' }
case 'PullRequestEvent': {
const pullRequest = event.payload.pull_request
switch (event.payload.action) {
case 'opened':
case 'reopened':
return getPullRequestIconAndColor({
draft: pullRequest.draft,
state: 'open',
merged: false,
merged_at: undefined,
mergeable_state: pullRequest.mergeable_state,
})
// case 'closed': return getPullRequestIconAndColor({ state: 'closed' } as GitHubPullRequest);
// case 'assigned':
// case 'unassigned':
// case 'labeled':
// case 'unlabeled':
// case 'edited':
default:
return getPullRequestIconAndColor(pullRequest)
}
}
case 'PullRequestReviewCommentEvent':
case 'PullRequestReviewEvent': {
return {
...getPullRequestIconAndColor(event.payload.pull_request),
subIcon: 'comment-discussion',
}
}
case 'PushEvent':
return { icon: 'code' }
case 'ReleaseEvent':
return isTagMainEvent(event)
? getTagIconAndColor()
: getReleaseIconAndColor()
case 'WatchEvent':
case 'WatchEvent:OneUserMultipleRepos':
return { icon: 'star', color: 'yellow' }
default: {
const message = `Unknown event type: ${(event as any).type}`
console.error(message)
return { icon: 'mark-github' }
}
}
}
export function mergeEventsPreservingEnhancement(
newItems: EnhancedGitHubEvent[],
prevItems: EnhancedGitHubEvent[],
) {
return sortEvents(
_.uniqBy(_.concat(newItems, prevItems), 'id').map(item => {
const newItem = newItems.find(i => i.id === item.id)
const existingItem = prevItems.find(i => i.id === item.id)
if (!(newItem && existingItem)) return item
const mergedItem = {
forceUnreadLocally: existingItem.forceUnreadLocally,
last_read_at: existingItem.last_read_at,
last_unread_at: existingItem.last_unread_at,
saved: existingItem.saved,
unread: existingItem.unread,
...newItem,
}
return _.isEqual(mergedItem, existingItem) ? existingItem : mergedItem
}),
)
}

View File

@@ -4,14 +4,58 @@ import _ from 'lodash'
import {
EnhancedGitHubIssueOrPullRequest,
EnhancementCache,
GitHubIssue,
GitHubIssueOrPullRequest,
GitHubIssueOrPullRequestSubjectType,
GitHubPullRequest,
IssueOrPullRequestColumnSubscription,
IssueOrPullRequestPayloadEnhancement,
} from '../../types'
import { getOwnerAndRepo } from './shared'
import {
getIssueIconAndColor,
getOwnerAndRepo,
getPullRequestIconAndColor,
} from './shared'
import { getRepoFullNameFromUrl } from './url'
export const issueOrPullRequestSubjectTypes: GitHubIssueOrPullRequestSubjectType[] = [
'Issue',
'PullRequest',
]
export function getIssueOrPullRequestIconAndColor(
type: GitHubIssueOrPullRequestSubjectType,
issueOrPullRequest: GitHubIssueOrPullRequest,
) {
return type === 'PullRequest'
? getPullRequestIconAndColor(issueOrPullRequest as GitHubPullRequest)
: getIssueIconAndColor(issueOrPullRequest as GitHubIssue)
}
export function mergeIssuesOrPullRequestsPreservingEnhancement(
newItems: EnhancedGitHubIssueOrPullRequest[],
prevItems: EnhancedGitHubIssueOrPullRequest[],
) {
return sortIssuesOrPullRequests(
_.uniqBy(_.concat(newItems || [], prevItems || []), 'id').map(item => {
const newItem = (newItems || []).find(i => i.id === item.id)
const existingItem = prevItems.find(i => i.id === item.id)
if (!(newItem && existingItem)) return item
const mergedItem = {
forceUnreadLocally: existingItem.forceUnreadLocally,
last_read_at: existingItem.last_read_at,
last_unread_at: existingItem.last_unread_at,
saved: existingItem.saved,
unread: existingItem.unread,
...newItem,
}
return _.isEqual(mergedItem, existingItem) ? existingItem : mergedItem
}),
)
}
export function getIssueOrPullRequestSubjectType(
item: GitHubIssueOrPullRequest,
): GitHubIssueOrPullRequestSubjectType | null {
@@ -185,7 +229,7 @@ export function getGitHubIssueSearchQuery(
if (repoFullName) queryArr.push(`repo:${repoFullName}`)
if (subjectType === 'Issue') queryArr.push('is:issue')
if (subjectType === 'PullRequest') queryArr.push('is:pr')
else if (subjectType === 'PullRequest') queryArr.push('is:pr')
return queryArr.join(' ')
}

View File

@@ -4,12 +4,241 @@ import _ from 'lodash'
import {
EnhancedGitHubNotification,
EnhancementCache,
GitHubIcon,
GitHubIssue,
GitHubIssueOrPullRequest,
GitHubNotification,
GitHubNotificationReason,
GitHubNotificationSubjectType,
GitHubPullRequest,
NotificationPayloadEnhancement,
ThemeColors,
} from '../../types'
import { getOwnerAndRepo } from './shared'
import { capitalize } from '../shared'
import {
getCommitIconAndColor,
getIssueIconAndColor,
getOwnerAndRepo,
getPullRequestIconAndColor,
getReleaseIconAndColor,
} from './shared'
import { getCommentIdFromUrl } from './url'
export const notificationReasons: GitHubNotificationReason[] = [
'assign',
'author',
'comment',
'invitation',
'manual',
'mention',
'review_requested',
'security_alert',
'state_change',
'subscribed',
'team_mention',
]
export const notificationSubjectTypes: GitHubNotificationSubjectType[] = [
'Commit',
'Issue',
'PullRequest',
'Release',
'RepositoryInvitation',
'RepositoryVulnerabilityAlert',
]
export function getNotificationSubjectType(
notification: GitHubNotification,
): GitHubNotificationSubjectType | null {
if (!(notification && notification.subject && notification.subject.type))
return null
return notification.subject.type
}
export function getNotificationIconAndColor(
notification: GitHubNotification,
payload: GitHubIssueOrPullRequest | undefined,
): { icon: GitHubIcon; color?: keyof ThemeColors; tooltip: string } {
const { subject } = notification
const { type } = subject
switch (type) {
case 'Commit':
return getCommitIconAndColor()
case 'Issue':
return getIssueIconAndColor(payload as GitHubIssue)
case 'PullRequest':
return getPullRequestIconAndColor(payload as GitHubPullRequest)
case 'Release':
return getReleaseIconAndColor()
case 'RepositoryInvitation':
return {
icon: 'mail',
color: 'brown',
tooltip: 'Repository invitation',
}
case 'RepositoryVulnerabilityAlert':
return {
icon: 'alert',
color: 'yellow',
tooltip: 'Repository vulnerability alert',
}
default: {
const message = `Unknown event type: ${(event as any).type}`
console.error(message)
return { icon: 'bell', tooltip: '' }
}
}
}
export function getNotificationReasonMetadata<
T extends GitHubNotificationReason
>(
reason: T,
): {
color: keyof ThemeColors
reason: T
label: string
fullDescription: string
// smallDescription: string
} {
switch (reason) {
case 'assign':
return {
reason,
color: 'pink',
fullDescription: 'You were assigned to the thread',
// smallDescription: 'You were assigned',
label: 'Assigned',
}
case 'author':
return {
reason,
color: 'lightRed',
fullDescription: 'You created the thread',
// smallDescription: 'You created',
label: 'Author',
}
case 'comment':
return {
reason,
color: 'blue',
fullDescription: 'You commented on the thread',
// smallDescription: 'You commented',
label: 'Commented',
}
case 'invitation':
return {
reason,
color: 'brown',
fullDescription:
'You accepted an invitation to contribute to the repository',
// smallDescription: 'You were invited',
label: 'Invited',
}
case 'manual':
return {
reason,
color: 'teal',
fullDescription: 'You manually subscribed to the thread',
// smallDescription: 'You subscribed manually',
label: 'Manual',
}
case 'mention':
return {
reason,
color: 'orange',
fullDescription: 'You were @mentioned in the thread',
// smallDescription: 'You were mentioned',
label: 'Mentioned',
}
case 'state_change':
return {
reason,
color: 'purple',
fullDescription: 'You opened or closed the issue/pr',
// smallDescription: 'You opened/closed',
label: 'State changed',
}
case 'subscribed':
return {
reason,
color: 'blueGray',
fullDescription: "You're watching the repository",
// smallDescription: 'You are watching',
label: 'Watching',
}
case 'team_mention':
return {
reason,
color: 'yellow',
fullDescription: 'Your team was mentioned in the thread',
// smallDescription: 'Team mentioned',
label: 'Team mentioned',
}
case 'review_requested':
return {
reason,
color: 'yellow',
fullDescription: 'Someone requested your review in the pull request',
// smallDescription: 'Review requested',
label: 'Review requested',
}
case 'security_alert':
return {
reason,
color: 'red',
fullDescription: 'Potential security vulnerability found',
// smallDescription: 'Security alert',
label: 'Security',
}
default:
return {
reason,
color: 'gray',
fullDescription: '',
// smallDescription: '',
label: capitalize(reason),
}
}
}
export function mergeNotificationsPreservingEnhancement(
newItems: EnhancedGitHubNotification[],
prevItems: EnhancedGitHubNotification[],
) {
return sortNotifications(
_.uniqBy(_.concat(newItems, prevItems), 'id').map(item => {
const newItem = newItems.find(i => i.id === item.id)
const existingItem = prevItems.find(i => i.id === item.id)
if (!(newItem && existingItem)) return item
const mergedItem = {
forceUnreadLocally: existingItem.forceUnreadLocally,
last_read_at: existingItem.last_read_at,
last_unread_at: existingItem.last_unread_at,
saved: existingItem.saved,
unread: existingItem.unread,
...newItem,
}
return _.isEqual(mergedItem, existingItem) ? existingItem : mergedItem
}),
)
}
export async function getNotificationsEnhancementMap(
notifications: EnhancedGitHubNotification[],
{

View File

@@ -1,4 +1,5 @@
import gravatar from 'gravatar'
import _ from 'lodash'
import qs from 'qs'
import {
@@ -10,10 +11,13 @@ import {
EnhancedGitHubIssueOrPullRequest,
EnhancedGitHubNotification,
GitHubAPIHeaders,
GitHubEventSubjectType,
GitHubIcon,
GitHubNotificationSubjectType,
GitHubPullRequest,
IssueOrPullRequestColumnSubscription,
NotificationColumnSubscription,
ThemeColors,
} from '../../types'
import { getSteppedSize } from '../shared'
import { getGitHubIssueSearchQuery } from './issues'
@@ -526,3 +530,148 @@ export function getBranchNameFromRef(ref: string | undefined) {
.slice(2)
.join('/')
}
export function getCommitIconAndColor(): {
icon: GitHubIcon
color?: keyof ThemeColors
tooltip: string
} {
return { icon: 'git-commit', color: 'brown', tooltip: 'Commit' }
}
export function getReleaseIconAndColor(): {
icon: GitHubIcon
color?: keyof ThemeColors
tooltip: string
} {
return {
icon: 'rocket',
color: 'pink',
tooltip: 'Release',
}
}
export function getTagIconAndColor(): {
icon: GitHubIcon
color?: keyof ThemeColors
tooltip: string
} {
return {
icon: 'tag',
color: 'gray',
tooltip: 'Tag',
}
}
export function getPullRequestIconAndColor(pullRequest: {
draft: GitHubPullRequest['draft']
state: GitHubPullRequest['state']
merged: GitHubPullRequest['merged'] | undefined
merged_at: GitHubPullRequest['merged_at'] | undefined
mergeable_state: GitHubPullRequest['mergeable_state'] | undefined
}): { icon: GitHubIcon; color?: keyof ThemeColors; tooltip: string } {
const draft = isDraft(pullRequest)
const merged = !!(pullRequest.merged || pullRequest.merged_at)
const state = merged ? 'merged' : pullRequest.state
switch (state) {
case 'open':
return {
icon: 'git-pull-request',
color: draft ? 'gray' : 'green',
tooltip: `Open${draft ? ' draft' : ''} pull request`,
}
case 'closed':
return {
icon: 'git-pull-request',
color: 'lightRed',
tooltip: `Closed${draft ? ' draft' : ''} pull request`,
}
case 'merged':
return {
icon: 'git-merge',
color: 'purple',
tooltip: `Merged pull request`,
}
default:
return {
icon: 'git-pull-request',
tooltip: 'Pull Request',
}
}
}
export function getIssueIconAndColor(issue: {
state?: GitHubPullRequest['state']
merged_at?: GitHubPullRequest['merged_at']
}): { icon: GitHubIcon; color?: keyof ThemeColors; tooltip: string } {
const { state } = issue
if (isPullRequest(issue)) {
return getPullRequestIconAndColor(issue as GitHubPullRequest)
}
switch (state) {
case 'open':
return {
icon: 'issue-opened',
color: 'green',
tooltip: `Open issue`,
}
case 'closed':
return {
icon: 'issue-closed',
color: 'lightRed',
tooltip: 'Closed issue',
}
default:
return { icon: 'issue-opened', tooltip: 'Issue' }
}
}
export function getSubjectTypeMetadata<
T extends GitHubEventSubjectType | GitHubNotificationSubjectType
>(
subjectType: T,
): {
color?: keyof ThemeColors
label: string
subjectType: T
} {
switch (
subjectType as GitHubEventSubjectType | GitHubNotificationSubjectType
) {
case 'PullRequestReview': {
return {
label: 'Review',
subjectType,
}
}
case 'RepositoryInvitation': {
return {
label: 'Invitation',
subjectType,
}
}
case 'RepositoryVulnerabilityAlert': {
return {
label: 'Security Alert',
subjectType,
}
}
default: {
return {
label: _.startCase(subjectType),
subjectType,
}
}
}
}

View File

@@ -1,3 +1,4 @@
export * from './filters'
export * from './github'
export * from './graphql'
export * from './shared'

View File

@@ -187,6 +187,7 @@ export interface GitHubIssue {
title: string
labels: GitHubLabel[]
state: 'open' | 'closed'
draft?: boolean
locked: boolean
milestone?: GitHubMilestone | null
comments: number