Skip to content

Commit

Permalink
Merge branch 'plausible:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
karlosmid authored Jul 2, 2024
2 parents c938b0f + c871759 commit dff4865
Show file tree
Hide file tree
Showing 232 changed files with 14,790 additions and 4,246 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build-private-images-ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ concurrency:

jobs:
build:
if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'deploy-to-staging') }}
if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'preview') }}
runs-on: buildjet-16vcpu-ubuntu-2204

permissions:
Expand Down Expand Up @@ -46,7 +46,7 @@ jobs:

- name: Build and push
id: docker_build
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-private-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:

- name: Build and push
id: docker_build
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-public-images-ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:

- name: Build and push
id: docker_build
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-public-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:

- name: Build and push
id: docker_build
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/comment-preview-url.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Add preview environment URL to PR

on:
pull_request:
types: [labeled]

permissions:
pull-requests: write

env:
PR_NUMBER: ${{ github.event.number }}

jobs:
comment:
if: ${{ contains(github.event.pull_request.labels.*.name, 'preview') }}
runs-on: ubuntu-latest
steps:
- name: Comment with preview URL
uses: thollander/actions-comment-pull-request@v2.5.0
with:
message: |
<div align="center">
|Preview environment👷🏼‍♀️🏗️ |
|:-:|
| [PR-${{env.PR_NUMBER}}](https://pr-${{env.PR_NUMBER}}.review.plausible.io)
</div>
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
erlang 26.2.1
elixir 1.16.0-otp-26
erlang 27.0
elixir 1.17.1-otp-27
nodejs 21.0.0
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@ All notable changes to this project will be documented in this file.
## Unreleased

### Added
- Icons for browsers plausible/analytics#4239
- Automatic custom property selection in the dashboard Properties report
- Add `does_not_contain` filter support to dashboard

### Removed
- Deprecate `ECTO_IPV6` and `ECTO_CH_IPV6` env vars in CE plausible/analytics#4245

### Changed

- Increase hourly request limit for API keys in CE from 600 to 1000000 (practically removing the limit) plausible/analytics#4200
- Make TCP connections try IPv6 first with IPv4 fallback in CE plausible/analytics#4245
- `is` and `is not` filters in dashboard no longer support wildcards. Use contains/does not contain filter instead.
- `bounce_rate` metric now returns 0 instead of null for event:page breakdown when page has never been entry page.

### Fixed

- Fix access to Stats API feature in CE plausible/analytics#4244

## v2.1.1 - 2024-06-06

### Added
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# platform specific, it makes sense to build it in the docker

#### Builder
FROM hexpm/elixir:1.16.0-erlang-26.2.1-alpine-3.18.4 as buildcontainer
FROM hexpm/elixir:1.17.1-erlang-27.0-alpine-3.18.6 as buildcontainer

ARG MIX_ENV=ce

Expand All @@ -28,7 +28,7 @@ COPY mix.lock ./
COPY config ./config
RUN mix local.hex --force && \
mix local.rebar --force && \
mix deps.get --only prod && \
mix deps.get --only ${MIX_ENV} && \
mix deps.compile

COPY assets/package.json assets/package-lock.json ./assets/
Expand All @@ -54,7 +54,7 @@ COPY rel rel
RUN mix release plausible

# Main Docker Image
FROM alpine:3.18.4
FROM alpine:3.18.6
LABEL maintainer="plausible.io <hello@plausible.io>"

ARG BUILD_METADATA={}
Expand Down
3 changes: 2 additions & 1 deletion assets/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ if (container) {
background: container.dataset.background,
isDbip: container.dataset.isDbip === 'true',
flags: JSON.parse(container.dataset.flags),
validIntervalsByPeriod: JSON.parse(container.dataset.validIntervalsByPeriod)
validIntervalsByPeriod: JSON.parse(container.dataset.validIntervalsByPeriod),
shared: !!container.dataset.sharedLinkAuth,
}

const loggedIn = container.dataset.loggedIn === 'true'
Expand Down
4 changes: 3 additions & 1 deletion assets/js/dashboard/components/combobox.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ function optionId(index) {

export default function PlausibleCombobox(props) {
const [options, setOptions] = useState([])
const [loading, setLoading] = useState(false)
const [isLoading, setLoading] = useState(false)
const [isOpen, setOpen] = useState(false)
const [input, setInput] = useState('')
const [highlightedIndex, setHighlightedIndex] = useState(0)
const searchRef = useRef(null)
const containerRef = useRef(null)
const listRef = useRef(null)

const loading = isLoading || !!props.forceLoading

const visibleOptions = [...options]
if (props.freeChoice && input.length > 0 && options.every(option => option.value !== input)) {
visibleOptions.push({value: input, label: input, freeChoice: true})
Expand Down
15 changes: 8 additions & 7 deletions assets/js/dashboard/components/filter-operator-selector.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Fragment } from "react";

import { FILTER_OPERATIONS } from "../util/filters";
import { FILTER_OPERATIONS, FILTER_OPERATIONS_DISPLAY_NAMES } from "../util/filters";
import { Menu, Transition } from "@headlessui/react";
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { isFreeChoiceFilter, supportsIsNot } from "../util/filters";
Expand All @@ -9,20 +9,20 @@ import classNames from "classnames";
export default function FilterOperatorSelector(props) {
const filterName = props.forFilter

function renderTypeItem(type, shouldDisplay) {
function renderTypeItem(operation, shouldDisplay) {
return (
shouldDisplay && (
<Menu.Item>
{({ active }) => (
<span
onClick={() => props.onSelect(type)}
onClick={() => props.onSelect(operation)}
className={classNames("cursor-pointer block px-4 py-2 text-sm", {
"bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100": active,
"text-gray-700 dark:text-gray-200": !active
}
)}
>
{type}
{FILTER_OPERATIONS_DISPLAY_NAMES[operation]}
</span>
)}
</Menu.Item>
Expand All @@ -40,8 +40,8 @@ export default function FilterOperatorSelector(props) {
{({ open }) => (
<>
<div className="w-full">
<Menu.Button className="inline-flex justify-between items-center w-full rounded-md border border-gray-300 dark:border-gray-500 shadow-sm px-4 py-2 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-850 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 dark:focus:ring-offset-gray-900 focus:ring-indigo-500">
{props.selectedType}
<Menu.Button className="inline-flex justify-between items-center w-full rounded-md border border-gray-300 dark:border-gray-500 shadow-sm px-4 py-2 bg-white dark:bg-gray-800 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-850 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 dark:focus:ring-offset-gray-900 focus:ring-indigo-500 text-left">
{FILTER_OPERATIONS_DISPLAY_NAMES[props.selectedType]}
<ChevronDownIcon className="-mr-2 ml-2 h-4 w-4 text-gray-500 dark:text-gray-400" aria-hidden="true" />
</Menu.Button>
</div>
Expand All @@ -58,12 +58,13 @@ export default function FilterOperatorSelector(props) {
>
<Menu.Items
static
className="z-10 origin-top-left absolute left-0 mt-2 w-full rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 focus:outline-none"
className="z-10 origin-top-left absolute left-0 mt-2 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<div className="py-1">
{renderTypeItem(FILTER_OPERATIONS.is, true)}
{renderTypeItem(FILTER_OPERATIONS.isNot, supportsIsNot(filterName))}
{renderTypeItem(FILTER_OPERATIONS.contains, isFreeChoiceFilter(filterName))}
{renderTypeItem(FILTER_OPERATIONS.does_not_contain, isFreeChoiceFilter(filterName))}
</div>
</Menu.Items>
</Transition>
Expand Down
15 changes: 15 additions & 0 deletions assets/js/dashboard/custom-hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useEffect, useRef } from 'react';

// A custom hook that behaves like `useEffect`, but
// the function does not run on the initial render.
export function useMountedEffect(fn, deps) {
const mounted = useRef(false)

useEffect(() => {
if (mounted.current) {
fn()
} else {
mounted.current = true
}
}, deps)
}
23 changes: 13 additions & 10 deletions assets/js/dashboard/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
formattedFilters,
EVENT_PROPS_PREFIX,
getPropertyKeyFromFilterKey,
getLabel
getLabel,
FILTER_OPERATIONS_DISPLAY_NAMES
} from "./util/filters"

const WRAPSTATE = { unwrapped: 0, waiting: 1, wrapped: 2 }
Expand Down Expand Up @@ -41,10 +42,10 @@ function filterText(query, [operation, filterKey, clauses]) {
const formattedFilter = formattedFilters[filterKey]

if (formattedFilter) {
return <>{formattedFilter} {operation} {clauses.map((value) => <b key={value}>{getLabel(query.labels, filterKey, value)}</b>).reduce((prev, curr) => [prev, ' or ', curr])} </>
return <>{formattedFilter} {FILTER_OPERATIONS_DISPLAY_NAMES[operation]} {clauses.map((value) => <b key={value}>{getLabel(query.labels, filterKey, value)}</b>).reduce((prev, curr) => [prev, ' or ', curr])} </>
} else if (filterKey.startsWith(EVENT_PROPS_PREFIX)) {
const propKey = getPropertyKeyFromFilterKey(filterKey)
return <>Property <b>{propKey}</b> {operation} {clauses.map((label) => <b key={label}>{label}</b>).reduce((prev, curr) => [prev, ' or ', curr])} </>
return <>Property <b>{propKey}</b> {FILTER_OPERATIONS_DISPLAY_NAMES[operation]} {clauses.map((label) => <b key={label}>{label}</b>).reduce((prev, curr) => [prev, ' or ', curr])} </>
}

throw new Error(`Unknown filter: ${filterKey}`)
Expand Down Expand Up @@ -83,7 +84,7 @@ function filterDropdownOption(site, option) {
<Menu.Item key={option}>
{({ active }) => (
<Link
to={{ pathname: `/${encodeURIComponent(site.domain)}/filter/${option}`, search: window.location.search }}
to={{ pathname: `/filter/${option}`, search: window.location.search }}
className={classNames(
active ? 'bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100' : 'text-gray-800 dark:text-gray-300',
'block px-4 py-2 text-sm font-medium'
Expand All @@ -96,11 +97,11 @@ function filterDropdownOption(site, option) {
)
}

function DropdownContent({ history, site, query, wrapState }) {
function DropdownContent({ history, site, query, wrapped }) {
const [addingFilter, setAddingFilter] = useState(false);

if (wrapState === WRAPSTATE.unwrapped || addingFilter) {
let filterModals = {...FILTER_MODAL_TO_FILTER_GROUP}
if (wrapped === WRAPSTATE.unwrapped || addingFilter) {
let filterModals = { ...FILTER_MODAL_TO_FILTER_GROUP }
if (!site.propsAvailable) delete filterModals.props

return Object.keys(filterModals).map((option) => filterDropdownOption(site, option))
Expand All @@ -127,9 +128,11 @@ function Filters(props) {
const [viewport, setViewport] = useState(1080)

useEffect(() => {
handleResize()

window.addEventListener('resize', handleResize, false)
document.addEventListener('keyup', handleKeyup)

return () => {
window.removeEventListener('resize', handleResize, false)
document.removeEventListener("keyup", handleKeyup)
Expand Down Expand Up @@ -190,7 +193,7 @@ function Filters(props) {
title={`Edit filter: ${formattedFilters[type]}`}
className="flex w-full h-full items-center py-2 pl-3"
to={{
pathname: `/${encodeURIComponent(site.domain)}/filter/${FILTER_GROUP_TO_MODAL_TYPE[type]}`,
pathname: `/filter/${FILTER_GROUP_TO_MODAL_TYPE[type]}`,
search: window.location.search
}}
>
Expand Down Expand Up @@ -228,7 +231,7 @@ function Filters(props) {
}

function trackFilterMenu() {
window.plausible && window.plausible('Filter Menu: Open', {u: `${window.location.protocol}//${window.location.hostname}/:dashboard`})
window.plausible && window.plausible('Filter Menu: Open', { u: `${window.location.protocol}//${window.location.hostname}/:dashboard` })
}

function renderDropDown() {
Expand Down
3 changes: 2 additions & 1 deletion assets/js/dashboard/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom'

import { useMountedEffect } from './custom-hooks';
import Historical from './historical'
import Realtime from './realtime'
import {parseQuery} from './query'
Expand All @@ -23,7 +24,7 @@ function Dashboard(props) {
}
}, [])

useEffect(() => {
useMountedEffect(() => {
api.cancelAll()
setQuery(parseQuery(location.search, site))
updateLastLoadTimestamp()
Expand Down
32 changes: 16 additions & 16 deletions assets/js/dashboard/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,50 +28,50 @@ function ScrollToTop() {

export default function Router({ site, loggedIn, currentUserRole }) {
return (
<BrowserRouter>
<Route path="/:domain">
<BrowserRouter basename={site.shared ? `/share/${encodeURIComponent(site.domain)}` : encodeURIComponent(site.domain)}>
<Route path="/">
<ScrollToTop />
<Dash site={site} loggedIn={loggedIn} currentUserRole={currentUserRole} />
<Switch>
<Route exact path={["/:domain/sources", "/:domain/utm_mediums", "/:domain/utm_sources", "/:domain/utm_campaigns", "/:domain/utm_contents", "/:domain/utm_terms"]}>
<Route exact path={["/sources", "/utm_mediums", "/utm_sources", "/utm_campaigns", "/utm_contents", "/utm_terms"]}>
<SourcesModal site={site} />
</Route>
<Route exact path="/:domain/referrers/Google">
<Route exact path="/referrers/Google">
<GoogleKeywordsModal site={site} />
</Route>
<Route exact path="/:domain/referrers/:referrer">
<Route exact path="/referrers/:referrer">
<ReferrersDrilldownModal site={site} />
</Route>
<Route path="/:domain/pages">
<Route path="/pages">
<PagesModal site={site} />
</Route>
<Route path="/:domain/entry-pages">
<Route path="/entry-pages">
<EntryPagesModal site={site} />
</Route>
<Route path="/:domain/exit-pages">
<Route path="/exit-pages">
<ExitPagesModal site={site} />
</Route>
<Route path="/:domain/countries">
<ModalTable title="Top countries" site={site} endpoint={url.apiPath(site, '/countries')} filterKey="country" keyLabel="Country" renderIcon={renderCountryIcon} showPercentage={true}/>
<Route path="/countries">
<ModalTable title="Top countries" site={site} endpoint={url.apiPath(site, '/countries')} filterKey="country" keyLabel="Country" renderIcon={renderCountryIcon} showPercentage={true} />
</Route>
<Route path="/:domain/regions">
<Route path="/regions">
<ModalTable title="Top regions" site={site} endpoint={url.apiPath(site, '/regions')} filterKey="region" keyLabel="Region" renderIcon={renderRegionIcon} />
</Route>
<Route path="/:domain/cities">
<Route path="/cities">
<ModalTable title="Top cities" site={site} endpoint={url.apiPath(site, '/cities')} filterKey="city" keyLabel="City" renderIcon={renderCityIcon} />
</Route>
<Route path="/:domain/custom-prop-values/:prop_key">
<Route path="/custom-prop-values/:prop_key">
<PropsModal site={site} />
</Route>
<Route path="/:domain/conversions">
<Route path="/conversions">
<ConversionsModal site={site} />
</Route>
<Route path={["/:domain/filter/:field"]}>
<Route path={["/filter/:field"]}>
<FilterModal site={site} />
</Route>
</Switch>
</Route>
</BrowserRouter>
</BrowserRouter >
);
}

Expand Down
Loading

0 comments on commit dff4865

Please sign in to comment.