Skip to content

Commit

Permalink
Merge branch 'master' into feat/hackathon-3000-feed-properties
Browse files Browse the repository at this point in the history
# Conflicts:
#	frontend/src/scenes/notebooks/Notebook/Editor.tsx
#	frontend/src/scenes/notebooks/Notebook/utils.ts
#	frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx
#	frontend/src/scenes/persons/PersonFeedCanvas.tsx
#	frontend/src/types.ts
  • Loading branch information
benjackwhite committed Oct 26, 2023
2 parents ed651c4 + 4412c2c commit 3b3dbc1
Show file tree
Hide file tree
Showing 17 changed files with 719 additions and 198 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MAPLIBRE_STYLE_URL=https://api.example.com/style.json?key=mykey
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare global {
JS_POSTHOG_API_KEY?: string
JS_POSTHOG_HOST?: string
JS_POSTHOG_SELF_CAPTURE?: boolean
JS_MAPLIBRE_STYLE_URL?: string
JS_CAPTURE_TIME_TO_SEE_DATA?: boolean
JS_KEA_VERBOSE_LOGGING?: boolean
posthog?: posthog
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/lib/components/Map/Map.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Marker } from 'maplibre-gl'

import { Map, MapComponent } from './Map'

const meta: Meta<typeof Map> = {
title: 'Components/Map',
component: Map,
tags: ['autodocs'],
}
type Story = StoryObj<typeof Map>

const coordinates: [number, number] = [0.119167, 52.205276]

export const Unavailable: Story = {}

export const Basic: Story = {
render: (args) => (
<MapComponent
mapLibreStyleUrl="" // TODO: set this value for the publish storybook and visual regression tests
{...args}
/>
),
args: {
center: coordinates,
markers: [new Marker({ color: 'var(--primary)' }).setLngLat(coordinates)],
className: 'h-60',
},
}

export default meta
64 changes: 64 additions & 0 deletions frontend/src/lib/components/Map/Map.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useEffect, useRef } from 'react'
import { Map as RawMap, Marker } from 'maplibre-gl'
import useResizeObserver from 'use-resize-observer'

import 'maplibre-gl/dist/maplibre-gl.css'

/** Latitude and longtitude in degrees (+lat is east, -lat is west, +lon is south, -lon is north). */
export interface MapProps {
/** Coordinates to center the map on by default. */
center: [number, number]
/** Markers to show. */
markers?: Marker[]
/** Map container class names. */
className?: string
/** The map's MapLibre style. This must be a JSON object conforming to the schema described in the MapLibre Style Specification, or a URL to such JSON. */
mapLibreStyleUrl: string
}

export function Map({ className, ...rest }: Omit<MapProps, 'mapLibreStyleUrl'>): JSX.Element {
if (!window.JS_MAPLIBRE_STYLE_URL) {
return (
<div className={`w-full h-full flex flex-col items-center justify-center text-muted p-3 ${className}`}>
<h1>Map unavailable</h1>
<p>
The <code>MAPLIBRE_STYLE_URL</code> setting is not defined. Please configure this setting with a
valid MapLibre Style URL to display maps.
</p>
</div>
)
}

return <MapComponent mapLibreStyleUrl={window.JS_MAPLIBRE_STYLE_URL} className={className} {...rest} />
}

export function MapComponent({ center, markers, className, mapLibreStyleUrl }: MapProps): JSX.Element {
const mapContainer = useRef<HTMLDivElement>(null)
const map = useRef<RawMap | null>(null)

useEffect(() => {
map.current = new RawMap({
container: mapContainer.current as HTMLElement,
style: mapLibreStyleUrl,
center,
zoom: 4,
maxZoom: 10,
})
if (markers) {
for (const marker of markers) {
marker.addTo(map.current)
}
}
}, [])

useResizeObserver({
ref: mapContainer,
onResize: () => {
if (map.current) {
map.current.resize()
}
},
})

return <div ref={mapContainer} className={className} />
}
64 changes: 64 additions & 0 deletions frontend/src/scenes/notebooks/Nodes/NotebookNodeMap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Marker } from 'maplibre-gl'

import { NotebookNodeType } from '~/types'
import { createPostHogWidgetNode } from 'scenes/notebooks/Nodes/NodeWrapper'
import { personLogic } from 'scenes/persons/personLogic'
import { useValues } from 'kea'
import { LemonSkeleton } from '@posthog/lemon-ui'
import { NotFound } from 'lib/components/NotFound'
import { Map } from '../../../lib/components/Map/Map'
import { notebookNodeLogic } from './notebookNodeLogic'
import { NotebookNodeProps } from 'scenes/notebooks/Notebook/utils'
import { NotebookNodeEmptyState } from './components/NotebookNodeEmptyState'

const Component = ({ attributes }: NotebookNodeProps<NotebookNodeMapAttributes>): JSX.Element | null => {
const { id } = attributes
const { expanded } = useValues(notebookNodeLogic)

const logic = personLogic({ id })
const { person, personLoading } = useValues(logic)

if (personLoading) {
return <LemonSkeleton className="h-6" />
} else if (!person) {
return <NotFound object="person" />
}

if (!expanded) {
return null
}

const longtitude = person?.properties?.['$geoip_longitude']
const latitude = person?.properties?.['$geoip_latitude']
const personCoordinates: [number, number] | null =
!isNaN(longtitude) && !isNaN(latitude) ? [longtitude, latitude] : null

if (!personCoordinates) {
return <NotebookNodeEmptyState message="No map available." />
}

return (
<Map
center={personCoordinates}
markers={[new Marker({ color: 'var(--primary)' }).setLngLat(personCoordinates)]}
className="h-full"
/>
)
}

type NotebookNodeMapAttributes = {
id: string
}

export const NotebookNodeMap = createPostHogWidgetNode<NotebookNodeMapAttributes>({
nodeType: NotebookNodeType.Map,
titlePlaceholder: 'Location',
Component,
resizeable: true,
heightEstimate: 150,
expandable: true,
startExpanded: true,
attributes: {
id: {},
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type NotebookNodeEmptyStateProps = {
message: string
}

export function NotebookNodeEmptyState({ message }: NotebookNodeEmptyStateProps): JSX.Element {
return (
<div className="w-full h-full flex flex-col items-center justify-center text-muted p-3">
<i>{message}</i>
</div>
)
}
2 changes: 2 additions & 0 deletions frontend/src/scenes/notebooks/Notebook/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { NotebookNodeGroup } from '../Nodes/NotebookNodeGroup'
import { NotebookNodeCohort } from '../Nodes/NotebookNodeCohort'
import { NotebookNodePersonFeed } from '../Nodes/NotebookNodePersonFeed/NotebookNodePersonFeed'
import { NotebookNodeProperties } from '../Nodes/NotebookNodeProperties'
import { NotebookNodeMap } from '../Nodes/NotebookNodeMap'

const CustomDocument = ExtensionDocument.extend({
content: 'heading block*',
Expand Down Expand Up @@ -122,6 +123,7 @@ export function Editor(): JSX.Element {
BacklinkCommandsExtension,
NodeGapInsertionExtension,
NotebookNodePersonFeed,
NotebookNodeMap,
],
editorProps: {
handleDrop: (view, event, _slice, moved) => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/scenes/notebooks/Notebook/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export const textContent = (node: any): string => {
'ph-cohort': customOrTitleSerializer,
'ph-person-feed': customOrTitleSerializer,
'ph-properties': customOrTitleSerializer,
'ph-map': customOrTitleSerializer,
}

return getText(node, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { NotebooksListFilters } from 'scenes/notebooks/NotebooksTable/notebooksT

export const fromNodeTypeToLabel: Omit<
Record<NotebookNodeType, string>,
NotebookNodeType.Backlink | NotebookNodeType.PersonFeed | NotebookNodeType.Properties
NotebookNodeType.Backlink | NotebookNodeType.PersonFeed | NotebookNodeType.Properties | NotebookNodeType.Map
> = {
[NotebookNodeType.FeatureFlag]: 'Feature flags',
[NotebookNodeType.FeatureFlagCodeExample]: 'Feature flag Code Examples',
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/scenes/persons/PersonFeedCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ const PersonFeedCanvas = ({ person }: PersonFeedCanvasProps): JSX.Element => {
type: 'ph-properties',
attrs: { id: personId, nodeId: uuid() },
},
{
type: 'ph-map',
attrs: { id: personId, nodeId: uuid() },
},
],
},
},
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3083,6 +3083,7 @@ export enum NotebookNodeType {
Image = 'ph-image',
PersonFeed = 'ph-person-feed',
Properties = 'ph-properties',
Map = 'ph-map',
}

export type NotebookNodeResource = {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
"kea-test-utils": "^0.2.4",
"kea-waitfor": "^0.2.1",
"kea-window-values": "^3.0.0",
"maplibre-gl": "^3.5.1",
"md5": "^2.3.0",
"monaco-editor": "^0.39.0",
"papaparse": "^5.4.1",
Expand Down
Loading

0 comments on commit 3b3dbc1

Please sign in to comment.