From 8785b2ac83aa3df1c49b0896e7daf69c35f509ff Mon Sep 17 00:00:00 2001 From: Bruno Lemos Date: Fri, 21 Jun 2019 20:41:56 -0300 Subject: [PATCH] New filter: is:bot Closes https://github.com/devhubapp/devhub/issues/20 Related: https://github.com/devhubapp/devhub/issues/131 --- .../cards/IssueOrPullRequestCard.tsx | 6 +- .../src/components/columns/ColumnOptions.tsx | 65 +++++++++++++++++++ .../components/src/redux/actions/columns.ts | 7 ++ .../components/src/redux/reducers/columns.ts | 13 ++++ packages/core/src/helpers/filters.ts | 22 ++++++- packages/core/src/helpers/github/events.ts | 3 +- .../core/src/helpers/github/notifications.ts | 13 +--- packages/core/src/helpers/github/shared.ts | 55 ++++++++++++++-- packages/core/src/helpers/shared.ts | 11 ++++ packages/core/src/types/devhub.ts | 2 + 10 files changed, 173 insertions(+), 24 deletions(-) diff --git a/packages/components/src/components/cards/IssueOrPullRequestCard.tsx b/packages/components/src/components/cards/IssueOrPullRequestCard.tsx index 39a3b97f7..b691f433d 100644 --- a/packages/components/src/components/cards/IssueOrPullRequestCard.tsx +++ b/packages/components/src/components/cards/IssueOrPullRequestCard.tsx @@ -9,6 +9,7 @@ import { getGitHubURLForRepo, getIssueOrPullRequestIconAndColor, getIssueOrPullRequestNumberFromUrl, + getItemIsBot, getOwnerAndRepo, getRepoFullNameFromUrl, GitHubIssueOrPullRequestSubjectType, @@ -84,7 +85,6 @@ export const IssueOrPullRequestCard = React.memo( repository_url, saved, url, - user, html_url: htmlURL, } = issueOrPullRequest as EnhancedGitHubIssueOrPullRequest @@ -116,9 +116,7 @@ export const IssueOrPullRequestCard = React.memo( html_url: getGitHubURLForRepo(repoOwnerName!, repoName!)!, } - const isBot = Boolean( - user && user.login && user.login.indexOf('[bot]') >= 0, - ) + const isBot = getItemIsBot('issue_or_pr', issueOrPullRequest) const cardIconDetails = getIssueOrPullRequestIconAndColor( type, diff --git a/packages/components/src/components/columns/ColumnOptions.tsx b/packages/components/src/components/columns/ColumnOptions.tsx index 6acb68ecb..bd65b35e2 100644 --- a/packages/components/src/components/columns/ColumnOptions.tsx +++ b/packages/components/src/components/columns/ColumnOptions.tsx @@ -103,6 +103,7 @@ export interface ColumnOptionsProps { } export type ColumnOptionCategory = + | 'bot' | 'draft' | 'event_action' | 'inbox' @@ -183,6 +184,7 @@ export const ColumnOptions = React.memo((props: ColumnOptionsProps) => { 'unread', 'state', 'draft', + 'bot', _shouldShowInvolvesFilter && 'involves', 'subject_types', column.type === 'activity' && 'event_action', @@ -236,6 +238,7 @@ export const ColumnOptions = React.memo((props: ColumnOptionsProps) => { const setColummStateTypeFilter = useReduxAction( actions.setColummStateTypeFilter, ) + const setColummBotFilter = useReduxAction(actions.setColummBotFilter) const setColummDraftFilter = useReduxAction(actions.setColummDraftFilter) const setColummSubjectTypeFilter = useReduxAction( actions.setColummSubjectTypeFilter, @@ -816,6 +819,68 @@ export const ColumnOptions = React.memo((props: ColumnOptionsProps) => { ) })()} + {allColumnOptionCategories.includes('bot') && + (() => { + const bot = column.filters && column.filters.bot + const defaultBooleanValue = true + + const filteredItemsMetadata = getItemsFilterMetadata( + column.type, + getFilteredItems( + column.type, + allItems, + { ...column.filters, bot: undefined }, + getFilteredItemsOptions, + ), + ) + + return ( + toggleOpenedOptionCategory('bot') + : undefined + } + title="Bots" + // right={ + // bot === true + // ? 'Bots only' + // : bot === false + // ? 'Excluded' + // : 'Included' + // } + > + { + setColummBotFilter({ + columnId: column.id, + bot: typeof value === 'boolean' ? value : undefined, + }) + }} + right={getCheckboxRight(filteredItemsMetadata.bot)} + /> + + ) + })()} + {allColumnOptionCategories.includes('subject_types') && (() => { const filters = diff --git a/packages/components/src/redux/actions/columns.ts b/packages/components/src/redux/actions/columns.ts index 57047fb11..cced98ccc 100644 --- a/packages/components/src/redux/actions/columns.ts +++ b/packages/components/src/redux/actions/columns.ts @@ -100,6 +100,13 @@ export function setColummStateTypeFilter(payload: { return createAction('SET_COLUMN_STATE_FILTER', payload) } +export function setColummBotFilter(payload: { + columnId: string + bot: ColumnFilters['bot'] +}) { + return createAction('SET_COLUMN_BOT_FILTER', payload) +} + export function setColummDraftFilter(payload: { columnId: string draft: ColumnFilters['draft'] diff --git a/packages/components/src/redux/reducers/columns.ts b/packages/components/src/redux/reducers/columns.ts index 188c28759..83a4d0306 100644 --- a/packages/components/src/redux/reducers/columns.ts +++ b/packages/components/src/redux/reducers/columns.ts @@ -332,6 +332,19 @@ export const columnsReducer: Reducer = ( draft.updatedAt = new Date().toISOString() }) + case 'SET_COLUMN_BOT_FILTER': + return immer(state, draft => { + if (!draft.byId) return + + const column = draft.byId[action.payload.columnId] + if (!column) return + + column.filters = column.filters || {} + column.filters.bot = action.payload.bot + + draft.updatedAt = new Date().toISOString() + }) + case 'SET_COLUMN_DRAFT_FILTER': return immer(state, draft => { if (!draft.byId) return diff --git a/packages/core/src/helpers/filters.ts b/packages/core/src/helpers/filters.ts index 76cc79453..4f633581a 100644 --- a/packages/core/src/helpers/filters.ts +++ b/packages/core/src/helpers/filters.ts @@ -15,6 +15,7 @@ import { import { getIssueOrPullRequestState, getIssueOrPullRequestSubjectType, + getItemIsBot, getItemIssueOrPullRequest, getItemOwnersAndRepos, getItemSearchableStrings, @@ -306,9 +307,10 @@ function baseColumnHasAnyFilter(filters: BaseColumnFilters | undefined) { if (!filters) return false if (filters.clearedAt) return true + if (typeof filters.bot === 'boolean') return true + if (typeof filters.draft === 'boolean') return true if (typeof filters.private === 'boolean') return true if (typeof filters.saved === 'boolean') return true - if (typeof filters.draft === 'boolean') return true if (typeof filters.unread === 'boolean') return true if (filters.query) return true @@ -416,6 +418,12 @@ export function getFilteredIssueOrPullRequests( ) return false + if ( + typeof filters.bot === 'boolean' && + filters.bot !== getItemIsBot('issue_or_pr', item) + ) + return false + if ( (filters.draft === true && (!issueOrPR || filters.draft !== isDraft(issueOrPR))) || @@ -516,6 +524,12 @@ export function getFilteredNotifications( ) return false + if ( + typeof filters.bot === 'boolean' && + filters.bot !== getItemIsBot('notifications', item) + ) + return false + if ( (filters.draft === true && (!issueOrPR || filters.draft !== isDraft(issueOrPR))) || @@ -617,6 +631,12 @@ export function getFilteredEvents( ) return false + if ( + typeof filters.bot === 'boolean' && + filters.bot !== getItemIsBot('activity', item) + ) + return false + if ( (filters.draft === true && (!issueOrPR || filters.draft !== isDraft(issueOrPR))) || diff --git a/packages/core/src/helpers/github/events.ts b/packages/core/src/helpers/github/events.ts index 2a8c0fe5a..e8a8e5f0a 100644 --- a/packages/core/src/helpers/github/events.ts +++ b/packages/core/src/helpers/github/events.ts @@ -31,6 +31,7 @@ import { getBranchNameFromRef, getCommitIconAndColor, getIssueIconAndColor, + getItemIsBot, getOwnerAndRepo, getPullRequestIconAndColor, getReleaseIconAndColor, @@ -986,7 +987,7 @@ export function getGitHubEventSubItems(event: EnhancedGitHubEvent) { const isForcePush = isPush && (payload as GitHubPushEvent).forced const isPrivate = isEventPrivate(event) - const isBot = Boolean(actor.login && actor.login.indexOf('[bot]') >= 0) + const isBot = getItemIsBot('activity', event) // GitHub returns the wrong avatar_url for app bots on actor.avatar_url, // but the correct avatar on payload.abc.user.avatar_url, diff --git a/packages/core/src/helpers/github/notifications.ts b/packages/core/src/helpers/github/notifications.ts index 81cf206a3..f2c3484ff 100644 --- a/packages/core/src/helpers/github/notifications.ts +++ b/packages/core/src/helpers/github/notifications.ts @@ -19,6 +19,7 @@ import { capitalize, isNotificationPrivate } from '../shared' import { getCommitIconAndColor, getIssueIconAndColor, + getItemIsBot, getOwnerAndRepo, getPullRequestIconAndColor, getReleaseIconAndColor, @@ -590,17 +591,7 @@ export function getGitHubNotificationSubItems( (notification.repository.full_name || notification.repository.name)) || '' - const actor = - (comment && comment.user) || - (commit && commit.author) || - (release && release.author) || - (issue && issue.user) || - (pullRequest && pullRequest.user) || - null - - const isBot = Boolean( - actor && actor.login && actor.login.indexOf('[bot]') >= 0, - ) + const isBot = getItemIsBot('notifications', notification) return { comment, diff --git a/packages/core/src/helpers/github/shared.ts b/packages/core/src/helpers/github/shared.ts index 44d63f621..b0da2fc73 100644 --- a/packages/core/src/helpers/github/shared.ts +++ b/packages/core/src/helpers/github/shared.ts @@ -1025,6 +1025,49 @@ export function getItemOwnersAndRepos( } } +export function getItemIsBot( + type: ColumnSubscription['type'], + item: EnhancedItem, +): boolean { + if (!item) return false + + switch (type) { + case 'activity': { + const event = item as EnhancedGitHubEvent + const { actor } = event + return !!(actor && actor.login && actor.login.indexOf('[bot]') >= 0) + } + + case 'issue_or_pr': { + const issueOrPR = item as EnhancedGitHubIssueOrPullRequest + const { user } = issueOrPR + + return !!(user && user.login && user.login.indexOf('[bot]') >= 0) + } + + case 'notifications': { + const notification = item as EnhancedGitHubNotification + const { comment, commit, release, issue, pullRequest } = notification + + const actor = + (comment && comment.user) || + (commit && commit.author) || + (release && release.author) || + (issue && issue.user) || + (pullRequest && pullRequest.user) || + null + + return ( + !!(actor && actor.login && actor.login.indexOf('[bot]') >= 0) || + notification.reason === 'security_alert' + ) + } + + default: + return false + } +} + export function getFilteredItems( type: ColumnSubscription['type'] | undefined, items: EnhancedItem[], @@ -1081,6 +1124,7 @@ const _defaultItemsFilterMetadata: ItemsFilterMetadata = { closed: getDefaultItemFilterCountMetadata(), merged: getDefaultItemFilterCountMetadata(), }, + bot: getDefaultItemFilterCountMetadata(), draft: getDefaultItemFilterCountMetadata(), // involves: {}, subjectType: {}, // { issue: getDefaultItemFilterCountMetadata(), ... } @@ -1149,6 +1193,8 @@ export function getItemsFilterMetadata( if (isDraft(issueOrPR)) updateNestedCounter(result.draft) + if (getItemIsBot(type, item)) updateNestedCounter(result.bot) + if (subjectType) { if (!result.subjectType[subjectType]) result.subjectType[subjectType] = getDefaultItemFilterCountMetadata() @@ -1237,7 +1283,6 @@ export function getItemSearchableStrings( const issueOrPullRequest = getItemIssueOrPullRequest(type, item) let comment: GitHubComment | undefined - let isBot: boolean | undefined let release: Partial | undefined const id = item.id @@ -1286,7 +1331,7 @@ export function getItemSearchableStrings( forkRepoFullName, // forkee, // id, - isBot: _isBot, + // isBot, // isBranchMainEvent, // isForcePush, // isPrivate, @@ -1308,7 +1353,6 @@ export function getItemSearchableStrings( } = getGitHubEventSubItems(event) comment = _comment - isBot = _isBot release = _release strings.push(`${(actor && actor.login) || ''}`) @@ -1353,7 +1397,7 @@ export function getItemSearchableStrings( commit, createdAt, // id, - isBot: _isBot, + // isBot, // isPrivate, // isPrivateAndCantSee, // isRead, @@ -1370,7 +1414,6 @@ export function getItemSearchableStrings( } = getGitHubNotificationSubItems(notification) comment = _comment - isBot = _isBot release = _release as any // TODO: Fix type error if (commit) { @@ -1398,8 +1441,6 @@ export function getItemSearchableStrings( // } - if (isBot) strings.push('is:bot') - if (comment) { strings.push(`${(comment.user && comment.user.login) || ''}`) strings.push(`${comment.body || ''}`) diff --git a/packages/core/src/helpers/shared.ts b/packages/core/src/helpers/shared.ts index 9828d352f..115574d40 100644 --- a/packages/core/src/helpers/shared.ts +++ b/packages/core/src/helpers/shared.ts @@ -345,6 +345,7 @@ export function getSearchQueryFromFilter( const { // clearedAt, + bot, draft, owners, private: _private, @@ -432,6 +433,11 @@ export function getSearchQueryFromFilter( if (states) handleRecordFilter('is', states) + if (typeof bot === 'boolean') { + const n = getMaybeNegate(bot) + queries.push(`${n}is:bot`) + } + if (typeof draft === 'boolean') { const n = getMaybeNegate(draft) queries.push(`${n}is:draft`) @@ -676,6 +682,11 @@ export function getFilterFromSearchQuery( break } + case 'bot': { + filters.bot = !isNegated + break + } + case 'draft': { filters.draft = !isNegated break diff --git a/packages/core/src/types/devhub.ts b/packages/core/src/types/devhub.ts index ed50db81f..e437174f6 100644 --- a/packages/core/src/types/devhub.ts +++ b/packages/core/src/types/devhub.ts @@ -211,6 +211,7 @@ export type NotificationColumnSubscription = { }) export interface BaseColumnFilters { + bot?: boolean clearedAt?: string draft?: boolean // order?: Array<[string, 'asc' | 'desc']> @@ -441,6 +442,7 @@ export interface ItemsFilterMetadata { saved: ItemFilterCountMetadata state: Record draft: ItemFilterCountMetadata + bot: ItemFilterCountMetadata // items doesn't have enough info to correctly calculate this metadata // involves: Partial>