Skip to content

Commit

Permalink
feat(annotations): Annotations page 2.0 (#11482)
Browse files Browse the repository at this point in the history
* style(annotations): Revamp Annotations page

* Add annotations to API builder

* Re-add base of annotation modal

* Update `LemonModal` and `IconClose` for visual polish

* Fix missing export

* Fix and align Date and time + Scope fields

* Hook up all the logic to the annotation modal

* Add annotations page story

* Fix typos

* Make date picker fit in

* Prevent ugly text wrapping

* Sync `AnnotationType` with API

* Clarify experience of insight-scoped annotations

* Restore Cypress instrumentation

* Fix typing

* Remove `data-tooltip`

* Rewrite Annotations page description

* Improve edge case with downgrading annotation scope

* Remove redundant function in logic
  • Loading branch information
Twixes authored Aug 26, 2022
1 parent 6b40f9a commit b8652db
Show file tree
Hide file tree
Showing 32 changed files with 2,208 additions and 477 deletions.
36 changes: 36 additions & 0 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { parsePeopleParams, PeopleParamType } from 'scenes/trends/personsModalLo
import {
ActionType,
ActorType,
AnnotationType,
CohortType,
CombinedEventType,
DashboardCollaboratorType,
Expand Down Expand Up @@ -243,6 +244,15 @@ class ApiRequest {
return this.persons().addPathComponent('activity')
}

// # Annotations
public annotations(teamId?: TeamType['id']): ApiRequest {
return this.projectsDetail(teamId).addPathComponent('annotations')
}

public annotation(id: AnnotationType['id'], teamId?: TeamType['id']): ApiRequest {
return this.annotations(teamId).addPathComponent(id)
}

// # Feature flags
public featureFlags(teamId: TeamType['id']): ApiRequest {
return this.projectsDetail(teamId).addPathComponent('feature_flags')
Expand Down Expand Up @@ -729,6 +739,32 @@ const api = {
},
},

annotations: {
async get(annotationId: AnnotationType['id']): Promise<AnnotationType> {
return await new ApiRequest().annotation(annotationId).get()
},
async update(
annotationId: AnnotationType['id'],
data: Pick<AnnotationType, 'date_marker' | 'scope' | 'content'>
): Promise<AnnotationType> {
return await new ApiRequest().annotation(annotationId).update({ data })
},
async restore(annotationId: AnnotationType['id']): Promise<AnnotationType> {
return await new ApiRequest().annotation(annotationId).update({ data: { deleted: false } })
},
async list(): Promise<PaginatedResponse<AnnotationType>> {
return await new ApiRequest().annotations().get()
},
async create(
data: Pick<AnnotationType, 'date_marker' | 'scope' | 'content' | 'dashboard_item'>
): Promise<AnnotationType> {
return await new ApiRequest().annotations().create({ data })
},
determineDeleteEndpoint(): string {
return new ApiRequest().annotations().assembleEndpointUrl()
},
},

licenses: {
async get(licenseId: LicenseType['id']): Promise<LicenseType> {
return await new ApiRequest().license(licenseId).get()
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/lib/components/Annotations/annotationsLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const annotationsLogic = kea<annotationsLogicType>({
created_at: now(),
scope,
}),
deleteAnnotation: (id: string) => ({ id }),
deleteAnnotation: (id: AnnotationType['id']) => ({ id }),
updateDiffType: (dates: string[]) => ({ dates }),
setDiffType: (type: OpUnitType) => ({ type }),
}),
Expand All @@ -43,7 +43,6 @@ export const annotationsLogic = kea<annotationsLogicType>({
const params = {
...(props.insightNumericId ? { dashboardItemId: props.insightNumericId } : {}),
scope: AnnotationScope.Insight,
deleted: false,
}
const response = await api.get(
`api/projects/${teamLogic.values.currentTeamId}/annotations/?${toParams(params)}`
Expand All @@ -57,7 +56,7 @@ export const annotationsLogic = kea<annotationsLogicType>({
createAnnotation: (state, { content, date_marker, created_at, scope }) => [
...state,
{
id: getNextKey(state).toString(),
id: getNextKey(state),
content,
date_marker: date_marker,
created_at: created_at.toISOString(),
Expand All @@ -66,7 +65,7 @@ export const annotationsLogic = kea<annotationsLogicType>({
},
],
deleteAnnotation: (state, { id }) => {
if (parseInt(id) >= 0) {
if (id >= 0) {
return state.filter((a) => a.id !== id)
} else {
return state
Expand Down Expand Up @@ -111,7 +110,7 @@ export const annotationsLogic = kea<annotationsLogicType>({
actions.loadAnnotations()
},
deleteAnnotation: async ({ id }) => {
parseInt(id) >= 0 &&
id >= 0 &&
deleteWithUndo({
endpoint: `projects/${teamLogic.values.currentTeamId}/annotations`,
object: { name: 'Annotation', id },
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/lib/components/Annotations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ export function getNextKey(arr: AnnotationType[]): number {
if (arr.length === 0) {
return -1
}
const result = arr.reduce((prev, curr) => (parseInt(prev.id) < parseInt(curr.id) ? prev : curr))
if (parseInt(result.id) >= 0) {
const result = arr.reduce((prev, curr) => (prev.id < curr.id ? prev : curr))
if (result.id >= 0) {
return -1
} else {
return parseInt(result.id) - 1
return result.id - 1
}
}
13 changes: 10 additions & 3 deletions frontend/src/lib/components/LemonModal/LemonModal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,19 @@
.LemonModal__header {
margin: 1.5rem;
margin-bottom: 0;
padding-bottom: 0.5rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid var(--border);

h3 {
margin-bottom: 0;
margin-right: 1.5rem;
font-size: 1.125rem;
line-height: 1.5rem;
font-weight: 700;
}

p {
margin: 0.5rem 0 -0.25rem;
}
}

Expand All @@ -87,8 +94,8 @@
gap: 0.5rem;
border-top: 1px solid var(--border);
margin: 1.5rem;
margin-top: 0rem;
padding-top: 1rem;
margin-top: 0;
padding-top: 1.5rem;
white-space: nowrap;
align-items: center;
}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/components/LemonModal/LemonModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export function LemonModal({
width: width,
},
}}
appElement={document.getElementById('root') as HTMLElement}
>
{modalContent}
</Modal>
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/lib/components/LemonSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ export interface LemonSelectOption<T> {
value: T
label: string | JSX.Element
icon?: React.ReactElement
sideIcon?: React.ReactElement
disabled?: boolean
tooltip?: string
'data-attr'?: string
element?: React.ReactElement
element?: React.ReactElement // TODO: Unify with `label`
}

export type LemonSelectOptions<T> = LemonSelectSection<T>[] | LemonSelectOption<T>[]
Expand Down Expand Up @@ -123,6 +125,8 @@ export function LemonSelect<T>({
<LemonButton
key={index}
icon={option.icon}
sideIcon={option.sideIcon}
tooltip={option.tooltip}
onClick={() => {
if (option.value != localValue) {
onChange?.(option.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,4 @@ WithHighlightedRows.args = {
}

export const WithMandatorySorting = BasicTemplate.bind({})
WithMandatorySorting.args = { defaultSorting: { columnKey: 'name', order: 1 }, disableSortingCancellation: true }
WithMandatorySorting.args = { defaultSorting: { columnKey: 'name', order: 1 }, noSortingCancellation: true }
4 changes: 2 additions & 2 deletions frontend/src/lib/components/LemonTable/LemonTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface LemonTableProps<T extends Record<string, any>> {
/**
* By default sorting goes: 0. unsorted > 1. ascending > 2. descending > GOTO 0 (loop).
* With sorting cancellation disabled, GOTO 0 is replaced by GOTO 1. */
disableSortingCancellation?: boolean
noSortingCancellation?: boolean
/** Sorting order to start with. */
defaultSorting?: Sorting | null
/** Controlled sort order. */
Expand Down Expand Up @@ -91,7 +91,7 @@ export function LemonTable<T extends Record<string, any>>({
expandable,
showHeader = true,
uppercaseHeader = true,
disableSortingCancellation = false,
noSortingCancellation: disableSortingCancellation = false,
defaultSorting = null,
sorting,
onSort,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/components/LemonTag/LemonTag.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
color: var(--default);
line-height: 1rem;
white-space: nowrap;
cursor: default;

&.highlight {
background-color: var(--yellow-lightest);
Expand Down
10 changes: 6 additions & 4 deletions frontend/src/lib/components/LemonTextArea/LemonTextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ export interface LemonTextAreaProps
disabled?: boolean
ref?: React.Ref<HTMLTextAreaElement>
onChange?: (newValue: string) => void
onPressEnter?: (newValue: string) => void
/** Callback called when Cmd + Enter (or Ctrl + Enter) is pressed.
* This checks for Cmd/Ctrl, as opposed to LemonInput, to avoid blocking multi-line input. */
onPressCmdEnter?: (newValue: string) => void
minRows?: number
maxRows?: number
rows?: number
}

/** A `LemonRow`-based `textarea` component for multi-line text. */
/** A `textarea` component for multi-line text. */
export const LemonTextArea = React.forwardRef<HTMLTextAreaElement, LemonTextAreaProps>(function _LemonTextArea(
{ className, onChange, onFocus, onBlur, onPressEnter, minRows = 3, onKeyDown, ...textProps },
{ className, onChange, onFocus, onBlur, onPressCmdEnter: onPressEnter, minRows = 3, onKeyDown, ...textProps },
ref
): JSX.Element {
const _ref = useRef<HTMLTextAreaElement | null>(null)
Expand All @@ -37,7 +39,7 @@ export const LemonTextArea = React.forwardRef<HTMLTextAreaElement, LemonTextArea
ref={textRef}
className={clsx('LemonTextArea', className)}
onKeyDown={(e) => {
if (onPressEnter && e.key === 'Enter') {
if (onPressEnter && e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
onPressEnter(textProps.value?.toString() ?? '')
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1116,7 +1116,7 @@ export function IconClose(props: SvgIconProps): JSX.Element {
return (
<SvgIcon version="1.1" viewBox="0 0 24 24" {...props}>
<path
d="M16.9248 15.4656L13.4592 12L16.9248 8.53443C17.0251 8.43411 17.0251 8.26995 16.9248 8.16963L15.8304 7.07524C15.7301 6.97492 15.5659 6.97492 15.4656 7.07524L12 10.5408L8.53443 7.07524C8.43411 6.97492 8.26995 6.97492 8.16963 7.07524L7.07524 8.16963C6.97492 8.26995 6.97492 8.43411 7.07524 8.53443L10.5408 12L7.07524 15.4656C6.97492 15.5659 6.97492 15.7301 7.07524 15.8304L8.16963 16.9248C8.26995 17.0251 8.43411 17.0251 8.53443 16.9248L12 13.4592L15.4656 16.9248C15.5659 17.0251 15.7301 17.0251 15.8304 16.9248L16.9248 15.8304C17.0251 15.7301 17.0251 15.5659 16.9248 15.4656Z"
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"
fill="currentColor"
/>
</SvgIcon>
Expand Down
17 changes: 13 additions & 4 deletions frontend/src/lib/forms/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,22 @@ export type PureFieldProps = {
help?: React.ReactNode
/** Error message to be displayed */
error?: React.ReactNode
className?: string
children?: React.ReactNode
}

/** A "Pure" field - used when you want the Field styles without the Kea form functionality */
export const PureField = ({ label, info, error, help, showOptional, children }: PureFieldProps): JSX.Element => {
export const PureField = ({
label,
info,
error,
help,
showOptional,
className,
children,
}: PureFieldProps): JSX.Element => {
return (
<div className={clsx('Field flex flex-col gap-2', error && 'Field--error')}>
<div className={clsx('Field flex flex-col gap-2', className, error && 'Field--error')}>
{label ? (
<LemonLabel info={info} showOptional={showOptional}>
{label}
Expand All @@ -40,11 +49,11 @@ export const PureField = ({ label, info, error, help, showOptional, children }:

export type FieldProps = Omit<PureFieldProps, 'children' | 'error'> & Pick<KeaFieldProps, 'children' | 'name'>

export const Field = ({ name, help, ...keaFieldProps }: FieldProps): JSX.Element => {
export const Field = ({ name, help, className, ...keaFieldProps }: FieldProps): JSX.Element => {
/** Drop-in replacement antd template for kea forms */
const template: KeaFieldProps['template'] = ({ label, kids, error }) => {
return (
<PureField label={label} error={error} help={help}>
<PureField label={label} error={error} help={help} className={className}>
{kids}
</PureField>
)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/models/annotationsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const annotationsModel = kea<annotationsModelType>({
createGlobalAnnotation: (state, { content, date_marker, created_at, created_by, annotationScope }) => [
...state,
{
id: getNextKey(state).toString(),
id: getNextKey(state),
content,
date_marker: date_marker,
created_at: created_at.toISOString(),
Expand Down
Loading

0 comments on commit b8652db

Please sign in to comment.