Skip to content

Commit

Permalink
Add pinnable actions
Browse files Browse the repository at this point in the history
  • Loading branch information
robbie-c committed Aug 19, 2024
1 parent 79e71c0 commit d1c407f
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export const taxonomicFilterLogic = kea<taxonomicFilterLogicType>([
searchPlaceholder: 'actions',
type: TaxonomicFilterGroupType.Actions,
logic: actionsModel,
value: 'actions',
value: 'actionsSorted',
getName: (action: ActionType) => action.name || '',
getValue: (action: ActionType) => action.id,
getPopoverHeader: () => 'Action',
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/models/actionsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const actionsModel = kea<actionsModelType>([
connect({
values: [teamLogic, ['currentTeam']],
}),
loaders(({ props, values }) => ({
loaders(({ props, values, actions }) => ({
actions: {
__default: [] as ActionType[],
loadActions: async () => {
Expand All @@ -31,6 +31,22 @@ export const actionsModel = kea<actionsModelType>([
},
updateAction: (action: ActionType) => (values.actions || []).map((a) => (action.id === a.id ? action : a)),
},
pin: {
pinAction: async (action: ActionType) => {
const response = await api.actions.update(action.id, {
name: action.name,
pinned_at: new Date().toISOString(),
})
actions.updateAction(response)
},
unpinAction: async (action: ActionType) => {
const response = await api.actions.update(action.id, {
name: action.name,
pinned_at: null,
})
actions.updateAction(response)
},
},
})),
selectors(({ selectors }) => ({
actionsGrouped: [
Expand All @@ -51,6 +67,12 @@ export const actionsModel = kea<actionsModelType>([
(actions): Partial<Record<string | number, ActionType>> =>
Object.fromEntries(actions.map((action) => [action.id, action])),
],
actionsSorted: [
(s) => [s.actions],
(actions: ActionType[]): ActionType[] => {
return actions.sort((a, b) => (b.pinned_at ? 1 : 0) - (a.pinned_at ? 1 : 0))
},
],
})),
events(({ values, actions }) => ({
afterMount: () => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/scenes/actions/Action.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const MOCK_ACTION: ActionType = {
deleted: false,
is_calculating: false,
last_calculated_at: '2024-05-21T12:57:50.894221Z',
pinned_at: null,
}

const meta: Meta = {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/actions/actionsLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const actionsLogic = kea<actionsLogicType>([
actionsFiltered: [
(s) => [s.actions, s.filterType, s.searchTerm, s.user],
(actions, filterType, searchTerm, user) => {
let data = actions
let data: ActionType[] = actions
if (searchTerm) {
data = actionsFuse.search(searchTerm).map((result) => result.item)
}
Expand Down
22 changes: 20 additions & 2 deletions frontend/src/scenes/data-management/actions/ActionsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IconCheckCircle } from '@posthog/icons'
import { IconCheckCircle, IconPin, IconPinFilled } from '@posthog/icons'
import { LemonInput, LemonSegmentedButton } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import api from 'lib/api'
Expand Down Expand Up @@ -28,7 +28,7 @@ import { urls } from '../../urls'
export function ActionsTable(): JSX.Element {
const { currentTeam } = useValues(teamLogic)
const { actionsLoading } = useValues(actionsModel({ params: 'include_count=1' }))
const { loadActions } = useActions(actionsModel)
const { loadActions, pinAction, unpinAction } = useActions(actionsModel)

const { filterType, searchTerm, actionsFiltered, shouldShowEmptyState } = useValues(actionsLogic)
const { setFilterType, setSearchTerm } = useActions(actionsLogic)
Expand Down Expand Up @@ -56,6 +56,24 @@ export function ActionsTable(): JSX.Element {
}

const columns: LemonTableColumns<ActionType> = [
{
width: 0,
title: 'Pinned',
dataIndex: 'pinned_at',
sorter: (a: ActionType, b: ActionType) =>
(b.pinned_at ? new Date(b.pinned_at).getTime() : 0) -
(a.pinned_at ? new Date(a.pinned_at).getTime() : 0),
render: function Render(pinned, action) {
return (
<LemonButton
size="small"
onClick={pinned ? () => unpinAction(action) : () => pinAction(action)}
tooltip={pinned ? 'Unpin dashboard' : 'Pin dashboard'}
icon={pinned ? <IconPinFilled /> : <IconPin />}
/>
)
},
},
{
title: 'Name',
dataIndex: 'name',
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/toolbar/actions/actionsLogic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { toolbarConfigLogic } from '~/toolbar/toolbarConfigLogic'
import { ActionType } from '~/types'

const unsortedActions: ActionType[] = [
{ name: 'zoo', created_at: '', created_by: null, id: 1 },
{ name: 'middle', created_at: '', created_by: null, id: 2 },
{ name: 'begin', created_at: '', created_by: null, id: 3 },
{ name: 'zoo', created_at: '', created_by: null, id: 1, pinned_at: null },
{ name: 'middle', created_at: '', created_by: null, id: 2, pinned_at: null },
{ name: 'begin', created_at: '', created_by: null, id: 3, pinned_at: null },
]
const apiJson = { results: unsortedActions }

Expand Down
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ export interface ActionType {
action_id?: number // alias of id to make it compatible with event definitions uuid
bytecode?: any[]
bytecode_error?: string
pinned_at: string | null
}

/** Sync with plugin-server/src/types.ts */
Expand Down
2 changes: 1 addition & 1 deletion latest_migrations.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ contenttypes: 0002_remove_content_type_name
ee: 0016_rolemembership_organization_member
otp_static: 0002_throttling
otp_totp: 0002_auto_20190420_0723
posthog: 0457_datawarehousejoin_deleted_at_and_more
posthog: 0458_action_pinned_at
sessions: 0001_initial
social_django: 0010_uid_db_index
two_factor: 0007_auto_20201201_1019
12 changes: 12 additions & 0 deletions posthog/api/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from .forbid_destroy_model import ForbidDestroyModel
from .tagged_item import TaggedItemSerializerMixin, TaggedItemViewSetMixin
from datetime import datetime, UTC


class ActionStepJSONSerializer(serializers.Serializer):
Expand Down Expand Up @@ -58,6 +59,7 @@ class Meta:
"team_id",
"is_action",
"bytecode_error",
"pinned_at",
]
read_only_fields = [
"team_id",
Expand All @@ -77,6 +79,8 @@ def validate(self, attrs):
else:
attrs["team_id"] = self.context["view"].team_id
include_args = {"team_id": attrs["team_id"]}
if attrs.get("pinned_at") == "":
attrs["pinned_at"] = None

colliding_action_ids = list(
Action.objects.filter(name=attrs["name"], deleted=False, **include_args)
Expand Down Expand Up @@ -104,6 +108,14 @@ def create(self, validated_data: Any) -> Any:
return instance

def update(self, instance: Any, validated_data: dict[str, Any]) -> Any:
if validated_data.get("pinned_at"):
if instance.pinned_at:
# drop it from the update
del validated_data["pinned_at"]
else:
# ignore the user-provided timestamp, generate our own
validated_data["pinned_at"] = datetime.now(UTC).isoformat()

instance = super().update(instance, validated_data)

report_user_action(
Expand Down
17 changes: 17 additions & 0 deletions posthog/migrations/0458_action_pinned_at.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.14 on 2024-08-12 17:24

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("posthog", "0452_organization_logo"),
]

operations = [
migrations.AddField(
model_name="action",
name="pinned_at",
field=models.DateTimeField(blank=True, default=None, null=True),
),
]
3 changes: 3 additions & 0 deletions posthog/models/action/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Meta:
bytecode: models.JSONField = models.JSONField(null=True, blank=True)
bytecode_error: models.TextField = models.TextField(blank=True, null=True)
steps_json: models.JSONField = models.JSONField(null=True, blank=True)
pinned_at: models.DateTimeField = models.DateTimeField(blank=True, null=True, default=None)

# DEPRECATED: these were used before ClickHouse was our database
is_calculating: models.BooleanField = models.BooleanField(default=False)
Expand All @@ -71,6 +72,8 @@ def get_analytics_metadata(self):
"match_url_count": sum(1 if step.url else 0 for step in self.steps),
"has_properties": any(step.properties for step in self.steps),
"deleted": self.deleted,
"pinned": bool(self.pinned_at),
"pinned_at": self.pinned_at,
}

@property
Expand Down

0 comments on commit d1c407f

Please sign in to comment.