-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add and remove insight from dashboard modal #27138
Changes from 8 commits
9d860ed
231f40c
fef8545
c88a34c
2ac9946
f99f699
cfe7e10
2454dc0
687b20a
8f3a010
902d8c8
183944f
a85d1da
93085c0
9b3b9db
6a18e28
9a6adb9
91eb466
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { useActions, useValues } from 'kea' | ||
import { LemonButton } from 'lib/lemon-ui/LemonButton' | ||
import { LemonModal } from 'lib/lemon-ui/LemonModal' | ||
import { addInsightToDashboardLogic } from 'scenes/dashboard/addInsightToDasboardModalLogic' | ||
import { AddSavedInsightsToDashboard } from 'scenes/saved-insights/AddSavedInsightsToDashboard' | ||
import { urls } from 'scenes/urls' | ||
|
||
import { dashboardLogic } from './dashboardLogic' | ||
|
||
export function AddInsightToDashboardModal(): JSX.Element { | ||
const { hideAddInsightToDashboardModal } = useActions(addInsightToDashboardLogic) | ||
const { addInsightToDashboardModalVisible } = useValues(addInsightToDashboardLogic) | ||
const { dashboard } = useValues(dashboardLogic) | ||
return ( | ||
<LemonModal | ||
title="Add insight to dashboard" | ||
onClose={hideAddInsightToDashboardModal} | ||
isOpen={addInsightToDashboardModalVisible} | ||
footer={ | ||
<> | ||
<LemonButton type="secondary" data-attr="dashboard-cancel" onClick={hideAddInsightToDashboardModal}> | ||
Cancel | ||
</LemonButton> | ||
<LemonButton | ||
type="primary" | ||
data-attr="dashboard-add-new-insight" | ||
to={urls.insightNew(undefined, dashboard?.id)} | ||
> | ||
New insight | ||
</LemonButton> | ||
</> | ||
} | ||
> | ||
<AddSavedInsightsToDashboard /> | ||
</LemonModal> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { actions, kea, path, reducers } from 'kea' | ||
|
||
import type { addInsightToDashboardLogicType } from './addInsightToDasboardModalLogicType' | ||
|
||
export const addInsightToDashboardLogic = kea<addInsightToDashboardLogicType>([ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: the file is called |
||
path(['scenes', 'dashboard', 'addInsightToDashboardLogic']), | ||
actions({ | ||
showAddInsightToDashboardModal: true, | ||
hideAddInsightToDashboardModal: true, | ||
}), | ||
reducers({ | ||
addInsightToDashboardModalVisible: [ | ||
false, | ||
{ | ||
showAddInsightToDashboardModal: () => true, | ||
hideAddInsightToDashboardModal: () => false, | ||
}, | ||
], | ||
}), | ||
]) |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file seems to contain a lot of duplication from the saved insights page. Can we dry up the code and at least import all the metadata from there? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree and updated |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
import './SavedInsights.scss' | ||
|
||
import { IconMinusSmall, IconPlusSmall } from '@posthog/icons' | ||
import { useActions, useValues } from 'kea' | ||
import { ActivityLog } from 'lib/components/ActivityLog/ActivityLog' | ||
import { Alerts } from 'lib/components/Alerts/views/Alerts' | ||
import { ObjectTags } from 'lib/components/ObjectTags/ObjectTags' | ||
import { TZLabel } from 'lib/components/TZLabel' | ||
import { LemonButton } from 'lib/lemon-ui/LemonButton' | ||
import { LemonDivider } from 'lib/lemon-ui/LemonDivider' | ||
import { LemonTable, LemonTableColumn, LemonTableColumns } from 'lib/lemon-ui/LemonTable' | ||
import { createdAtColumn, createdByColumn } from 'lib/lemon-ui/LemonTable/columnUtils' | ||
import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' | ||
import { isNonEmptyObject } from 'lib/utils' | ||
import { dashboardLogic } from 'scenes/dashboard/dashboardLogic' | ||
import { SavedInsightsEmptyState } from 'scenes/insights/EmptyStates' | ||
import { useSummarizeInsight } from 'scenes/insights/summarizeInsight' | ||
import { organizationLogic } from 'scenes/organizationLogic' | ||
import { overlayForNewInsightMenu } from 'scenes/saved-insights/newInsightsMenu' | ||
import { SavedInsightsFilters } from 'scenes/saved-insights/SavedInsightsFilters' | ||
import { SceneExport } from 'scenes/sceneTypes' | ||
import { urls } from 'scenes/urls' | ||
|
||
import { isNodeWithSource } from '~/queries/utils' | ||
import { ActivityScope, QueryBasedInsightModel, SavedInsightsTabs } from '~/types' | ||
|
||
import { addSavedInsightsModalLogic } from './addSavedInsightsModalLogic' | ||
import { QUERY_TYPES_METADATA } from './SavedInsights' | ||
import { savedInsightsLogic } from './savedInsightsLogic' | ||
|
||
interface NewInsightButtonProps { | ||
dataAttr: string | ||
} | ||
|
||
const INSIGHTS_PER_PAGE = 15 | ||
|
||
export const scene: SceneExport = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no need for a scene export. Scenes are "routes" in our app and since this is a modal, we don't need one. Assuming this is a leftover from copying the saved insights page. |
||
component: AddSavedInsightsToDashboard, | ||
logic: savedInsightsLogic, | ||
} | ||
|
||
export function InsightIcon({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This icon can reuse the one from the saved insights page. |
||
insight, | ||
className, | ||
}: { | ||
insight: QueryBasedInsightModel | ||
className?: string | ||
}): JSX.Element | null { | ||
let Icon: (props?: any) => JSX.Element | null = () => null | ||
|
||
if ('query' in insight && isNonEmptyObject(insight.query)) { | ||
const insightType = isNodeWithSource(insight.query) ? insight.query.source.kind : insight.query.kind | ||
const insightMetadata = QUERY_TYPES_METADATA[insightType] | ||
Icon = insightMetadata && insightMetadata.icon | ||
} | ||
|
||
return Icon ? <Icon className={className} /> : null | ||
} | ||
|
||
export function NewInsightButton({ dataAttr }: NewInsightButtonProps): JSX.Element { | ||
return ( | ||
<LemonButton | ||
type="primary" | ||
to={urls.insightNew()} | ||
sideAction={{ | ||
dropdown: { | ||
placement: 'bottom-end', | ||
className: 'new-insight-overlay', | ||
actionable: true, | ||
overlay: overlayForNewInsightMenu(dataAttr), | ||
}, | ||
'data-attr': 'saved-insights-new-insight-dropdown', | ||
}} | ||
data-attr="saved-insights-new-insight-button" | ||
size="small" | ||
icon={<IconPlusSmall />} | ||
> | ||
New insight | ||
</LemonButton> | ||
) | ||
} | ||
|
||
export function AddSavedInsightsToDashboard(): JSX.Element { | ||
const { modalPage } = useValues(addSavedInsightsModalLogic) | ||
const { setModalPage } = useActions(addSavedInsightsModalLogic) | ||
|
||
const { insights, count, insightsLoading, filters, sorting, alertModalId, dashboardUpdatesInProgress } = | ||
useValues(savedInsightsLogic) | ||
const { setSavedInsightsFilters, addInsightToDashboard, removeInsightFromDashboard } = | ||
useActions(savedInsightsLogic) | ||
|
||
const { hasTagging } = useValues(organizationLogic) | ||
const { dashboard } = useValues(dashboardLogic) | ||
|
||
const summarizeInsight = useSummarizeInsight() | ||
|
||
const { tab } = filters | ||
|
||
const startCount = (modalPage - 1) * INSIGHTS_PER_PAGE + 1 | ||
const endCount = Math.min(modalPage * INSIGHTS_PER_PAGE, count) | ||
|
||
const columns: LemonTableColumns<QueryBasedInsightModel> = [ | ||
{ | ||
key: 'id', | ||
width: 32, | ||
render: function renderType(_, insight) { | ||
return <InsightIcon insight={insight} className="text-muted text-2xl" /> | ||
}, | ||
}, | ||
{ | ||
title: 'Name', | ||
dataIndex: 'name', | ||
key: 'name', | ||
render: function renderName(name: string, insight) { | ||
return ( | ||
<> | ||
<LemonTableLink | ||
to={urls.insightView(insight.short_id)} | ||
title={<>{name || <i>{summarizeInsight(insight.query)}</i>}</>} | ||
description={insight.description} | ||
/> | ||
</> | ||
) | ||
}, | ||
}, | ||
...(hasTagging | ||
? [ | ||
{ | ||
title: 'Tags', | ||
dataIndex: 'tags' as keyof QueryBasedInsightModel, | ||
key: 'tags', | ||
render: function renderTags(tags: string[]) { | ||
return <ObjectTags tags={tags} staticOnly /> | ||
}, | ||
}, | ||
] | ||
: []), | ||
...(tab === SavedInsightsTabs.Yours | ||
? [] | ||
: [ | ||
createdByColumn() as LemonTableColumn< | ||
QueryBasedInsightModel, | ||
keyof QueryBasedInsightModel | undefined | ||
>, | ||
]), | ||
createdAtColumn() as LemonTableColumn<QueryBasedInsightModel, keyof QueryBasedInsightModel | undefined>, | ||
{ | ||
title: 'Last modified', | ||
sorter: true, | ||
dataIndex: 'last_modified_at', | ||
render: function renderLastModified(last_modified_at: string) { | ||
return ( | ||
<div className="whitespace-nowrap">{last_modified_at && <TZLabel time={last_modified_at} />}</div> | ||
) | ||
}, | ||
}, | ||
{ | ||
width: 0, | ||
render: function Render(_, insight) { | ||
const isInDashboard = dashboard?.tiles.some((tile) => tile.insight?.id === insight.id) | ||
return ( | ||
<LemonButton | ||
type="secondary" | ||
status={isInDashboard ? 'danger' : 'default'} | ||
loading={dashboardUpdatesInProgress[insight.id]} | ||
size="small" | ||
fullWidth | ||
onClick={(e) => { | ||
e.preventDefault() | ||
if (dashboardUpdatesInProgress[insight.id]) { | ||
return | ||
} | ||
isInDashboard | ||
? removeInsightFromDashboard(insight, dashboard?.id || 0) | ||
: addInsightToDashboard(insight, dashboard?.id || 0) | ||
}} | ||
> | ||
{isInDashboard ? <IconMinusSmall /> : <IconPlusSmall />} | ||
</LemonButton> | ||
) | ||
}, | ||
}, | ||
] | ||
|
||
return ( | ||
<div className="saved-insights"> | ||
{tab === SavedInsightsTabs.History ? ( | ||
<ActivityLog scope={ActivityScope.INSIGHT} /> | ||
) : tab === SavedInsightsTabs.Alerts ? ( | ||
<Alerts alertId={alertModalId} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't necessary here. |
||
) : ( | ||
<> | ||
<SavedInsightsFilters /> | ||
<LemonDivider className="my-4" /> | ||
<div className="flex justify-between mb-4 gap-2 flex-wrap mt-2 items-center"> | ||
<span className="text-muted-alt"> | ||
{count | ||
? `${startCount}${endCount - startCount > 1 ? '-' + endCount : ''} of ${count} insight${ | ||
count === 1 ? '' : 's' | ||
}` | ||
: null} | ||
</span> | ||
</div> | ||
{!insightsLoading && insights.count < 1 ? ( | ||
<SavedInsightsEmptyState /> | ||
) : ( | ||
<> | ||
<LemonTable | ||
loading={insightsLoading} | ||
columns={columns} | ||
dataSource={insights.results} | ||
pagination={{ | ||
controlled: true, | ||
currentPage: modalPage, | ||
pageSize: INSIGHTS_PER_PAGE, | ||
entryCount: count, | ||
onForward: () => setModalPage(modalPage + 1), | ||
onBackward: () => setModalPage(modalPage - 1), | ||
}} | ||
sorting={sorting} | ||
onSort={(newSorting) => | ||
setSavedInsightsFilters({ | ||
order: newSorting | ||
? `${newSorting.order === -1 ? '-' : ''}${newSorting.columnKey}` | ||
: undefined, | ||
}) | ||
} | ||
rowKey="id" | ||
loadingSkeletonRows={INSIGHTS_PER_PAGE} | ||
nouns={['insight', 'insights']} | ||
/> | ||
</> | ||
)} | ||
</> | ||
)} | ||
</div> | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit