mirror of
https://github.com/BillyOutlast/posthog.git
synced 2026-02-04 03:01:23 +01:00
feat(customers): Create NotebookNodeRelatedGroups (#40426)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
8b4cf4f2d1
commit
9dfb72fc7b
161
bin/create-notebook-node.sh
Executable file
161
bin/create-notebook-node.sh
Executable file
@@ -0,0 +1,161 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to create a new PostHog notebook node
|
||||
# Usage: ./bin/create-notebook-node.sh
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Get the script's directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# Ask for node name
|
||||
echo -e "${BLUE}Creating a new PostHog notebook node${NC}"
|
||||
echo ""
|
||||
read -p "Enter the node name (PascalCase, e.g., 'RelatedGroups'): " NODE_NAME
|
||||
|
||||
if [ -z "$NODE_NAME" ]; then
|
||||
echo "Error: Node name cannot be empty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Convert PascalCase to kebab-case for the node type
|
||||
# e.g., RelatedGroups -> related-groups
|
||||
NODE_TYPE=$(echo "$NODE_NAME" | sed 's/\([A-Z]\)/-\1/g' | sed 's/^-//' | tr '[:upper:]' '[:lower:]')
|
||||
NODE_TYPE="ph-$NODE_TYPE"
|
||||
|
||||
# Ask for human-readable label
|
||||
echo ""
|
||||
read -p "Enter the human-readable label (e.g., 'Related groups'): " NODE_LABEL
|
||||
|
||||
if [ -z "$NODE_LABEL" ]; then
|
||||
echo "Error: Node label cannot be empty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Creating notebook node:${NC}"
|
||||
echo " Node name: $NODE_NAME"
|
||||
echo " Node type: $NODE_TYPE"
|
||||
echo " Label: $NODE_LABEL"
|
||||
echo ""
|
||||
|
||||
# Define file paths
|
||||
NODES_DIR="$PROJECT_ROOT/frontend/src/scenes/notebooks/Nodes"
|
||||
NODE_FILE="$NODES_DIR/NotebookNode$NODE_NAME.tsx"
|
||||
EDITOR_FILE="$PROJECT_ROOT/frontend/src/scenes/notebooks/Notebook/Editor.tsx"
|
||||
TYPES_FILE="$PROJECT_ROOT/frontend/src/scenes/notebooks/types.ts"
|
||||
FILTER_FILE="$PROJECT_ROOT/frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx"
|
||||
UTILS_FILE="$PROJECT_ROOT/frontend/src/scenes/notebooks/utils.ts"
|
||||
|
||||
# Step 1: Create the node component file
|
||||
echo -e "${BLUE}1. Creating node component file...${NC}"
|
||||
|
||||
cat > "$NODE_FILE" << 'EOF'
|
||||
import { useValues } from 'kea'
|
||||
|
||||
import { NotebookNodeProps, NotebookNodeType } from '../types'
|
||||
import { createPostHogWidgetNode } from './NodeWrapper'
|
||||
import { notebookNodeLogic } from './notebookNodeLogic'
|
||||
|
||||
const Component = ({ attributes }: NotebookNodeProps<NotebookNode__NODE_NAME__Attributes>): JSX.Element | null => {
|
||||
const { expanded } = useValues(notebookNodeLogic)
|
||||
|
||||
if (!expanded) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
{/* TODO: Implement component */}
|
||||
<p>__NODE_NAME__ component - implement your UI here</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type NotebookNode__NODE_NAME__Attributes = {
|
||||
// TODO: Add your attributes here
|
||||
}
|
||||
|
||||
export const NotebookNode__NODE_NAME__ = createPostHogWidgetNode<NotebookNode__NODE_NAME__Attributes>({
|
||||
nodeType: NotebookNodeType.__NODE_NAME__,
|
||||
titlePlaceholder: '__TITLE_PLACEHOLDER__',
|
||||
Component,
|
||||
resizeable: false,
|
||||
expandable: true,
|
||||
startExpanded: true,
|
||||
attributes: {
|
||||
// TODO: Add your attribute definitions here
|
||||
},
|
||||
})
|
||||
EOF
|
||||
|
||||
# Replace placeholders
|
||||
sed -i '' "s/__NODE_NAME__/$NODE_NAME/g" "$NODE_FILE"
|
||||
sed -i '' "s/__TITLE_PLACEHOLDER__/$NODE_LABEL/g" "$NODE_FILE"
|
||||
|
||||
echo " Created: $NODE_FILE"
|
||||
|
||||
# Step 2: Add import and extension to Editor.tsx
|
||||
echo -e "${BLUE}2. Updating Editor.tsx...${NC}"
|
||||
|
||||
# Add import statement after the last NotebookNode import
|
||||
IMPORT_LINE="import { NotebookNode$NODE_NAME } from '../Nodes/NotebookNode$NODE_NAME'"
|
||||
LAST_IMPORT_LINE=$(grep -n "^import { NotebookNode" "$EDITOR_FILE" | tail -1 | cut -d: -f1)
|
||||
|
||||
# Use awk to insert the import line
|
||||
awk -v line="$LAST_IMPORT_LINE" -v text="$IMPORT_LINE" 'NR==line{print; print text; next}1' "$EDITOR_FILE" > "$EDITOR_FILE.tmp" && mv "$EDITOR_FILE.tmp" "$EDITOR_FILE"
|
||||
|
||||
# Add the node to the extensions array (after the last node, before the closing bracket)
|
||||
# Find the line with the closing bracket of the extensions array
|
||||
EXTENSIONS_CLOSE=$(grep -n "^ \]$" "$EDITOR_FILE" | head -1 | cut -d: -f1)
|
||||
|
||||
# Use awk to insert the extension line before the closing bracket
|
||||
awk -v line="$EXTENSIONS_CLOSE" -v text=" NotebookNode$NODE_NAME," 'NR==line{print text; print; next}1' "$EDITOR_FILE" > "$EDITOR_FILE.tmp" && mv "$EDITOR_FILE.tmp" "$EDITOR_FILE"
|
||||
|
||||
echo " Updated: $EDITOR_FILE"
|
||||
|
||||
# Step 3: Add to NotebookNodeType enum
|
||||
echo -e "${BLUE}3. Updating NotebookNodeType enum...${NC}"
|
||||
|
||||
ENUM_LINE=$(grep -n "ZendeskTickets = 'ph-zendesk-tickets'," "$TYPES_FILE" | cut -d: -f1)
|
||||
|
||||
# Use awk to insert the enum entry
|
||||
awk -v line="$ENUM_LINE" -v text=" $NODE_NAME = '$NODE_TYPE'," 'NR==line{print; print text; next}1' "$TYPES_FILE" > "$TYPES_FILE.tmp" && mv "$TYPES_FILE.tmp" "$TYPES_FILE"
|
||||
|
||||
echo " Updated: $TYPES_FILE"
|
||||
|
||||
# Step 4: Add to fromNodeTypeToLabel
|
||||
echo -e "${BLUE}4. Updating ContainsTypeFilter.tsx...${NC}"
|
||||
|
||||
FILTER_LINE=$(grep -n "\[NotebookNodeType.ZendeskTickets\]: 'Zendesk tickets'," "$FILTER_FILE" | cut -d: -f1)
|
||||
|
||||
# Use awk to insert the filter entry
|
||||
awk -v line="$FILTER_LINE" -v text=" [NotebookNodeType.$NODE_NAME]: '$NODE_LABEL'," 'NR==line{print; print text; next}1' "$FILTER_FILE" > "$FILTER_FILE.tmp" && mv "$FILTER_FILE.tmp" "$FILTER_FILE"
|
||||
|
||||
echo " Updated: $FILTER_FILE"
|
||||
|
||||
# Step 5: Add to customNodeTextSerializers
|
||||
echo -e "${BLUE}5. Updating utils.ts...${NC}"
|
||||
|
||||
UTILS_LINE=$(grep -n "\[NotebookNodeType.ZendeskTickets\]: customOrTitleSerializer," "$UTILS_FILE" | cut -d: -f1)
|
||||
|
||||
# Use awk to insert the serializer entry
|
||||
awk -v line="$UTILS_LINE" -v text=" [NotebookNodeType.$NODE_NAME]: customOrTitleSerializer," 'NR==line{print; print text; next}1' "$UTILS_FILE" > "$UTILS_FILE.tmp" && mv "$UTILS_FILE.tmp" "$UTILS_FILE"
|
||||
|
||||
echo " Updated: $UTILS_FILE"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Successfully created notebook node: $NODE_NAME${NC}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Implement the component in: $NODE_FILE"
|
||||
echo " 2. Define the attributes for your node"
|
||||
echo " 3. Add any custom settings if needed"
|
||||
echo " 4. Test your new node in a notebook"
|
||||
echo ""
|
||||
@@ -415,6 +415,10 @@ tools:
|
||||
bin_script: clickhouse-logs-init
|
||||
description: 'TODO: add description for clickhouse-logs-init'
|
||||
hidden: true
|
||||
create:notebook:node:
|
||||
bin_script: create-notebook-node.sh
|
||||
description: 'Create a new NotebookNode file and update types and editor references'
|
||||
hidden: true
|
||||
utilities:
|
||||
utilities:posthog-worktree:
|
||||
bin_script: posthog-worktree
|
||||
|
||||
@@ -11,14 +11,21 @@ import { PersonDisplay } from 'scenes/persons/PersonDisplay'
|
||||
import { groupsModel } from '~/models/groupsModel'
|
||||
import { ActorType } from '~/types'
|
||||
|
||||
interface Props {
|
||||
export interface RelatedGroupsProps {
|
||||
groupTypeIndex: number | null
|
||||
id: string
|
||||
type?: 'person' | 'group'
|
||||
pageSize?: number
|
||||
embedded?: boolean
|
||||
}
|
||||
|
||||
export function RelatedGroups({ groupTypeIndex, id, type, pageSize }: Props): JSX.Element {
|
||||
export function RelatedGroups({
|
||||
groupTypeIndex,
|
||||
id,
|
||||
type,
|
||||
pageSize,
|
||||
embedded = false,
|
||||
}: RelatedGroupsProps): JSX.Element {
|
||||
const { relatedActors, relatedPeople, relatedActorsLoading } = useValues(
|
||||
relatedGroupsLogic({ groupTypeIndex, id, type })
|
||||
)
|
||||
@@ -59,6 +66,7 @@ export function RelatedGroups({ groupTypeIndex, id, type, pageSize }: Props): JS
|
||||
<LemonTable
|
||||
dataSource={dataSource}
|
||||
columns={columns}
|
||||
embedded={embedded}
|
||||
rowKey="id"
|
||||
pagination={{ pageSize: pageSize || 30, hideOnSinglePage: true }}
|
||||
loading={relatedActorsLoading}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useValues } from 'kea'
|
||||
|
||||
import { RelatedGroups, RelatedGroupsProps } from 'scenes/groups/RelatedGroups'
|
||||
import { urls } from 'scenes/urls'
|
||||
|
||||
import { NotebookNodeProps, NotebookNodeType } from '../types'
|
||||
import { createPostHogWidgetNode } from './NodeWrapper'
|
||||
import { notebookNodeLogic } from './notebookNodeLogic'
|
||||
|
||||
const Component = ({ attributes }: NotebookNodeProps<NotebookNodeRelatedGroupsAttributes>): JSX.Element | null => {
|
||||
const { expanded } = useValues(notebookNodeLogic)
|
||||
|
||||
if (!expanded) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <RelatedGroups {...attributes} pageSize={10} embedded />
|
||||
}
|
||||
|
||||
type NotebookNodeRelatedGroupsAttributes = Pick<RelatedGroupsProps, 'id' | 'groupTypeIndex' | 'type'>
|
||||
|
||||
const href = ({ id, groupTypeIndex }: NotebookNodeRelatedGroupsAttributes): string => {
|
||||
if (typeof groupTypeIndex === 'number') {
|
||||
return urls.group(groupTypeIndex, id, false, 'related')
|
||||
}
|
||||
return urls.personByUUID(id) + '#activeTab=related'
|
||||
}
|
||||
|
||||
export const NotebookNodeRelatedGroups = createPostHogWidgetNode<NotebookNodeRelatedGroupsAttributes>({
|
||||
nodeType: NotebookNodeType.RelatedGroups,
|
||||
titlePlaceholder: 'Related groups',
|
||||
Component,
|
||||
resizeable: false,
|
||||
expandable: true,
|
||||
startExpanded: true,
|
||||
href,
|
||||
attributes: {
|
||||
id: {},
|
||||
groupTypeIndex: {},
|
||||
type: {},
|
||||
},
|
||||
})
|
||||
@@ -40,6 +40,7 @@ import { NotebookNodePersonProperties } from '../Nodes/NotebookNodePersonPropert
|
||||
import { NotebookNodePlaylist } from '../Nodes/NotebookNodePlaylist'
|
||||
import { NotebookNodeQuery } from '../Nodes/NotebookNodeQuery'
|
||||
import { NotebookNodeRecording } from '../Nodes/NotebookNodeRecording'
|
||||
import { NotebookNodeRelatedGroups } from '../Nodes/NotebookNodeRelatedGroups'
|
||||
import { NotebookNodeReplayTimestamp } from '../Nodes/NotebookNodeReplayTimestamp'
|
||||
import { NotebookNodeSurvey } from '../Nodes/NotebookNodeSurvey'
|
||||
import { NotebookNodeTaskCreate } from '../Nodes/NotebookNodeTaskCreate'
|
||||
@@ -143,6 +144,7 @@ export function Editor(): JSX.Element {
|
||||
NotebookNodeIssues,
|
||||
NotebookNodeUsageMetrics,
|
||||
NotebookNodeZendeskTickets,
|
||||
NotebookNodeRelatedGroups,
|
||||
]
|
||||
|
||||
if (hasCollapsibleSections) {
|
||||
|
||||
@@ -34,6 +34,7 @@ export const fromNodeTypeToLabel: Omit<
|
||||
[NotebookNodeType.Issues]: 'Issues',
|
||||
[NotebookNodeType.UsageMetrics]: 'Usage metrics',
|
||||
[NotebookNodeType.ZendeskTickets]: 'Zendesk tickets',
|
||||
[NotebookNodeType.RelatedGroups]: 'Related groups',
|
||||
}
|
||||
|
||||
export function ContainsTypeFilters({
|
||||
|
||||
@@ -62,6 +62,7 @@ export enum NotebookNodeType {
|
||||
Issues = 'ph-issues',
|
||||
UsageMetrics = 'ph-usage-metrics',
|
||||
ZendeskTickets = 'ph-zendesk-tickets',
|
||||
RelatedGroups = 'ph-related-groups',
|
||||
}
|
||||
|
||||
export type NotebookNodeResource = {
|
||||
|
||||
@@ -65,6 +65,7 @@ export const textContent = (node: RichContentNode): string => {
|
||||
[NotebookNodeType.TaskCreate]: customOrTitleSerializer,
|
||||
[NotebookNodeType.UsageMetrics]: customOrTitleSerializer,
|
||||
[NotebookNodeType.ZendeskTickets]: customOrTitleSerializer,
|
||||
[NotebookNodeType.RelatedGroups]: customOrTitleSerializer,
|
||||
}
|
||||
|
||||
return getText(node, {
|
||||
|
||||
@@ -51,6 +51,7 @@ const PersonFeedCanvas = ({ person }: PersonFeedCanvasProps): JSX.Element => {
|
||||
type: 'ph-person-properties',
|
||||
attrs: { id, distinctId, nodeId: uuid() },
|
||||
},
|
||||
{ type: 'ph-related-group', attrs: { id, nodeId: uuid(), type: 'groups' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -42,6 +42,16 @@ const GroupFeedCanvas = ({ group }: GroupFeedCanvas): JSX.Element => {
|
||||
nodeId: uuid(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ph-related-groups',
|
||||
attrs: {
|
||||
id: groupKey,
|
||||
groupTypeIndex: group.group_type_index,
|
||||
nodeId: uuid(),
|
||||
title: 'Related people',
|
||||
type: 'person',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user