Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge master
Browse files Browse the repository at this point in the history
apata committed Oct 10, 2024
2 parents c8f1562 + 45bcf55 commit 71e9c64
Showing 162 changed files with 6,926 additions and 5,395 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file.

## Unreleased

### Added
### Removed
### Changed
### Fixed

## v2.1.4 - 2024-10-08

### Added

- Add ability to review and revoke particular logged in user sessions
- Add ability to change password from user settings screen
- Add error logs for background jobs plausible/analytics#4657

### Changed

- Revised User Settings UI
- Default to `invite_only` for registration plausible/analytics#4616

### Fixed

- Fix cross-device file move in CSV exports/imports plausible/analytics#4640

## v2.1.3 - 2024-09-26

### Fixed
63 changes: 0 additions & 63 deletions assets/js/dashboard/historical.js

This file was deleted.

27 changes: 0 additions & 27 deletions assets/js/dashboard/index.js

This file was deleted.

69 changes: 69 additions & 0 deletions assets/js/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/** @format */

import React, { useState } from 'react'

import { useIsRealtimeDashboard } from './util/filters'
import VisitorGraph from './stats/graph/visitor-graph'
import Sources from './stats/sources'
import Pages from './stats/pages'
import Locations from './stats/locations'
import Devices from './stats/devices'
import { TopBar } from './nav-menu/top-bar'
import Behaviours from './stats/behaviours'

function DashboardStats({
importedDataInView,
updateImportedDataInView
}: {
importedDataInView?: boolean
updateImportedDataInView?: (v: boolean) => void
}) {
const statsBoxClass =
'stats-item relative w-full mt-6 p-4 flex flex-col bg-white dark:bg-gray-825 shadow-xl rounded'

return (
<>
<VisitorGraph updateImportedDataInView={updateImportedDataInView} />
<div className="w-full md:flex">
<div className={statsBoxClass}>
<Sources />
</div>
<div className={statsBoxClass}>
<Pages />
</div>
</div>

<div className="w-full md:flex">
<div className={statsBoxClass}>
<Locations />
</div>
<div className={statsBoxClass}>
<Devices />
</div>
</div>

<Behaviours importedDataInView={importedDataInView} />
</>
)
}

function Dashboard() {
const isRealTimeDashboard = useIsRealtimeDashboard()
const [importedDataInView, setImportedDataInView] = useState(false)

return (
<div className="mb-12">
<TopBar showCurrentVisitors={!isRealTimeDashboard} />
<DashboardStats
importedDataInView={
isRealTimeDashboard ? undefined : importedDataInView
}
updateImportedDataInView={
isRealTimeDashboard ? undefined : setImportedDataInView
}
/>
</div>
)
}

export default Dashboard
109 changes: 109 additions & 0 deletions assets/js/dashboard/nav-menu/top-bar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/** @format */

import React from 'react'
import {
render,
screen,
waitFor,
fireEvent,
waitForElementToBeRemoved
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { TestContextProviders } from '../../../test-utils/app-context-providers'
import { TopBar } from './top-bar'
import { MockAPI } from '../../../test-utils/mock-api'

const domain = 'dummy.site'
const domains = [domain, 'example.com', 'blog.example.com']

let mockAPI: MockAPI

beforeAll(() => {
global.IntersectionObserver = jest.fn(
() =>
({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn()
}) as unknown as IntersectionObserver
)
mockAPI = new MockAPI().start()
})

afterAll(() => {
mockAPI.stop()
})

beforeEach(() => {
mockAPI.clear()
mockAPI.get('/api/sites', { data: domains.map((domain) => ({ domain })) })
})

test('user can open and close site switcher', async () => {
render(<TopBar showCurrentVisitors={false} />, {
wrapper: (props) => (
<TestContextProviders siteOptions={{ domain }} {...props} />
)
})

const toggleSiteSwitcher = screen.getByRole('button', { name: domain })
await userEvent.click(toggleSiteSwitcher)
expect(screen.queryAllByRole('link').map((el) => el.textContent)).toEqual(
[
['example.com', '2'],
['blog.example.com', '3']
].map((a) => a.join(''))
)
expect(screen.queryAllByRole('menuitem').map((el) => el.textContent)).toEqual(
['Site Settings', 'View All']
)
await userEvent.click(toggleSiteSwitcher)
expect(screen.queryAllByRole('menuitem')).toEqual([])
})

test('user can open and close filters dropdown', async () => {
render(<TopBar showCurrentVisitors={false} />, {
wrapper: (props) => (
<TestContextProviders siteOptions={{ domain }} {...props} />
)
})

const toggleFilters = screen.getByRole('button', { name: /Filter/ })
await userEvent.click(toggleFilters)
expect(screen.queryAllByRole('menuitem').map((el) => el.textContent)).toEqual(
[
'Page',
'Source',
'Location',
'Screen size',
'Browser',
'Operating System',
'UTM tags',
'Goal',
'Hostname'
]
)
await userEvent.click(toggleFilters)
expect(screen.queryAllByRole('menuitem')).toEqual([])
})

test('current visitors renders when visitors are present and disappears after visitors are null', async () => {
mockAPI.get(`/api/stats/${domain}/current-visitors?`, 500)
render(<TopBar showCurrentVisitors={true} />, {
wrapper: (props) => (
<TestContextProviders siteOptions={{ domain }} {...props} />
)
})

await waitFor(() => {
expect(
screen.queryByRole('link', { name: /500 current visitors/ })
).toBeVisible()
})

mockAPI.get(`/api/stats/${domain}/current-visitors?`, null)
fireEvent(document, new CustomEvent('tick'))
await waitForElementToBeRemoved(() =>
screen.queryByRole('link', { name: /current visitors/ })
)
})
51 changes: 51 additions & 0 deletions assets/js/dashboard/nav-menu/top-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/** @format */

import React, { useRef } from 'react'
import SiteSwitcher from '../site-switcher'
import { useSiteContext } from '../site-context'
import { useUserContext } from '../user-context'
import CurrentVisitors from '../stats/current-visitors'
import QueryPeriodPicker from '../datepicker'
import Filters from '../filters'
import classNames from 'classnames'
import { useInView } from 'react-intersection-observer'

interface TopBarProps {
showCurrentVisitors: boolean
}

export function TopBar({ showCurrentVisitors }: TopBarProps) {
const site = useSiteContext()
const user = useUserContext()
const tooltipBoundary = useRef(null)
const { ref, inView } = useInView({ threshold: 0 })

return (
<>
<div id="stats-container-top" ref={ref} />
<div
className={classNames(
'relative top-0 sm:py-3 py-2 z-10',
!site.embedded &&
!inView &&
'sticky fullwidth-shadow bg-gray-50 dark:bg-gray-850'
)}
>
<div className="items-center w-full flex">
<div className="flex items-center w-full" ref={tooltipBoundary}>
<SiteSwitcher
site={site}
loggedIn={user.loggedIn}
currentUserRole={user.role}
/>
{showCurrentVisitors && (
<CurrentVisitors tooltipBoundary={tooltipBoundary.current} />
)}
<Filters />
</div>
<QueryPeriodPicker />
</div>
</div>
</>
)
}
42 changes: 0 additions & 42 deletions assets/js/dashboard/pinned-header-hoc.js

This file was deleted.

Loading

0 comments on commit 71e9c64

Please sign in to comment.