From 41b6105028163bc596cecce72967d9e4d162a097 Mon Sep 17 00:00:00 2001 From: Gokhan Sari Date: Sat, 4 Feb 2023 12:33:29 +0300 Subject: [PATCH] Improve sites page and add script adding instructions (#143) * Improve sites page and add script adding instructions * Fix the code highlighting issue in docs * Fix eslint and typescript issues * Fix eslint issues --- backend/Dockerfile | 5 ++ backend/development.Dockerfile | 4 ++ .../postgres/000001_create_tables.up.sql | 1 + backend/pkg/model/event.go | 2 - backend/pkg/model/site.go | 1 + backend/pkg/service/bootstrap/create_site.go | 1 + backend/pkg/service/event/create.go | 12 ++++ backend/pkg/service/site/delete.go | 12 +++- backend/pkg/service/site/site.go | 1 + .../service/sitereport/browsername/main.go | 4 -- backend/scripts/migrate-clickhouse | 7 ++ backend/scripts/migrate-postgres | 7 ++ frontend/@types/site.d.ts | 1 + frontend/components/Billing/index.tsx | 2 +- frontend/components/Empty/index.tsx | 22 ++++++ frontend/components/Markdown/index.tsx | 24 ++++++- frontend/components/SiteForm/index.tsx | 4 +- frontend/components/SiteReports/index.tsx | 16 ----- frontend/components/Sites/index.tsx | 11 ++- .../components/SyntaxHighlighter/index.tsx | 25 +++++++ frontend/components/index.ts | 2 + .../2_adding-the-script-to-your-website.md | 4 +- frontend/package.json | 1 + frontend/pages/_app.tsx | 7 -- frontend/pages/_document.tsx | 2 +- frontend/pages/sites/reports.tsx | 72 +++++++++++++++++-- frontend/styles/base.scss | 25 +++++++ frontend/yarn.lock | 2 +- 28 files changed, 231 insertions(+), 46 deletions(-) create mode 100755 backend/scripts/migrate-clickhouse create mode 100755 backend/scripts/migrate-postgres create mode 100644 frontend/components/Empty/index.tsx create mode 100644 frontend/components/SyntaxHighlighter/index.tsx diff --git a/backend/Dockerfile b/backend/Dockerfile index 67818e08..2006dd26 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -2,6 +2,8 @@ FROM golang:1.19.4 WORKDIR /usr/src/poeticmetric +RUN CGO_ENABLED=0 go install -tags 'clickhouse,postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.15.2 + COPY go.mod go.sum ./ RUN go mod download @@ -23,7 +25,10 @@ WORKDIR /poeticmetric COPY assets assets COPY migrations migrations +COPY scripts/migrate-clickhouse /usr/local/bin/migrate-clickhouse +COPY scripts/migrate-postgres /usr/local/bin/migrate-postgres +COPY --from=0 /go/bin/migrate /usr/local/bin/migrate COPY --from=0 /usr/src/poeticmetric/bin/migrator /usr/local/bin/poeticmetric-migrator COPY --from=0 /usr/src/poeticmetric/bin/rest-api /usr/local/bin/poeticmetric-rest-api COPY --from=0 /usr/src/poeticmetric/bin/scheduler /usr/local/bin/poeticmetric-scheduler diff --git a/backend/development.Dockerfile b/backend/development.Dockerfile index 4bd606d3..6993745e 100644 --- a/backend/development.Dockerfile +++ b/backend/development.Dockerfile @@ -4,6 +4,8 @@ RUN apt update && apt install -y postgresql-client WORKDIR /poeticmetric +RUN CGO_ENABLED=0 go install -tags 'clickhouse,postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.15.2 + RUN go install github.com/cespare/reflex@latest RUN go install github.com/go-delve/delve/cmd/dlv@latest @@ -15,6 +17,8 @@ COPY cmd cmd COPY migrations migrations COPY pkg pkg +COPY scripts/migrate-clickhouse /usr/local/bin/ +COPY scripts/migrate-postgres /usr/local/bin/ COPY scripts/run-tests /usr/local/bin/ COPY scripts/wait-for-it /usr/local/bin/ COPY docker-entrypoint.development.sh /usr/local/bin/docker-entrypoint.sh diff --git a/backend/migrations/postgres/000001_create_tables.up.sql b/backend/migrations/postgres/000001_create_tables.up.sql index 03b6645c..d535d95c 100644 --- a/backend/migrations/postgres/000001_create_tables.up.sql +++ b/backend/migrations/postgres/000001_create_tables.up.sql @@ -77,6 +77,7 @@ CREATE INDEX "idx_user_access_tokens_user_id" ON "user_access_tokens" ("user_id" CREATE TABLE "sites" ( "created_at" timestamptz NOT NULL, "domain" text UNIQUE, + "has_events" boolean NOT NULL, "id" bigserial, "is_public" boolean NOT NULL, "name" text, diff --git a/backend/pkg/model/event.go b/backend/pkg/model/event.go index 2ec1de96..2d892fe7 100644 --- a/backend/pkg/model/event.go +++ b/backend/pkg/model/event.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "log" url2 "net/url" "time" @@ -99,7 +98,6 @@ func (e *Event) FillFromUrl(url string, safeQueryParameters []string) { q := u.Query() for k := range u.Query() { - log.Println(safeQueryParametersMap[k]) if !safeQueryParametersMap[k] { q.Del(k) } diff --git a/backend/pkg/model/site.go b/backend/pkg/model/site.go index 8e84f1b8..a5032245 100644 --- a/backend/pkg/model/site.go +++ b/backend/pkg/model/site.go @@ -9,6 +9,7 @@ import ( type Site struct { CreatedAt time.Time Domain string + HasEvents bool Id uint64 IsPublic bool Name string diff --git a/backend/pkg/service/bootstrap/create_site.go b/backend/pkg/service/bootstrap/create_site.go index 2d8c2074..b9df6547 100644 --- a/backend/pkg/service/bootstrap/create_site.go +++ b/backend/pkg/service/bootstrap/create_site.go @@ -21,6 +21,7 @@ const eventsInBatch = 100 func createSite(dp *depot.Depot) error { modelSite := &model.Site{ Domain: "demo.yoursite.tld", + HasEvents: true, Id: 1, IsPublic: false, Name: "Demo Site", diff --git a/backend/pkg/service/event/create.go b/backend/pkg/service/event/create.go index 0020f6b1..60db2225 100644 --- a/backend/pkg/service/event/create.go +++ b/backend/pkg/service/event/create.go @@ -36,6 +36,7 @@ func Create(dp *depot.Depot, payload *CreatePayload) error { err = dp.Postgres(). Model(&model.Site{}). Select( + "has_events", "id", "safe_query_parameters", ). @@ -75,6 +76,17 @@ func Create(dp *depot.Depot, payload *CreatePayload) error { return err } + if !modelSite.HasEvents { + err = dp.Postgres(). + Model(&model.Site{}). + Where("id = ?", modelSite.Id). + Update("has_events", true). + Error + if err != nil { + return err + } + } + return nil } diff --git a/backend/pkg/service/site/delete.go b/backend/pkg/service/site/delete.go index 4bd4306d..8c4d3636 100644 --- a/backend/pkg/service/site/delete.go +++ b/backend/pkg/service/site/delete.go @@ -15,11 +15,19 @@ func Delete(dp *depot.Depot, id uint64) error { } err = dp.ClickHouse(). + Exec("optimize table events_buffer"). + Error + if err != nil { + return err + } + + err = dp.ClickHouse(). + Table("events"). Where("site_id = ?", id). - Delete(&model.Event{}). + Delete(nil). Error if err != nil { - // TODO: handle error + return err } return nil diff --git a/backend/pkg/service/site/site.go b/backend/pkg/service/site/site.go index cbcb5178..11ff293a 100644 --- a/backend/pkg/service/site/site.go +++ b/backend/pkg/service/site/site.go @@ -9,6 +9,7 @@ import ( type Site struct { CreatedAt time.Time `json:"createdAt"` Domain string `json:"domain"` + HasEvents bool `json:"hasEvents"` Id uint64 `json:"id"` IsPublic bool `json:"isPublic"` Name string `json:"name"` diff --git a/backend/pkg/service/sitereport/browsername/main.go b/backend/pkg/service/sitereport/browsername/main.go index b1a11634..a15df63b 100644 --- a/backend/pkg/service/sitereport/browsername/main.go +++ b/backend/pkg/service/sitereport/browsername/main.go @@ -1,8 +1,6 @@ package browsername import ( - "log" - "github.com/poeticmetric/poeticmetric/backend/pkg/depot" "github.com/poeticmetric/poeticmetric/backend/pkg/service/sitereport/filter" "github.com/poeticmetric/poeticmetric/backend/pkg/service/sitereport/pagination" @@ -26,8 +24,6 @@ type Report struct { } func Get(dp *depot.Depot, filters *filter.Filters, paginationCursor *PaginationCursor) (*Report, error) { - log.Println(paginationCursor) - report := &Report{} baseQuery := filter.Apply(dp, filters). diff --git a/backend/scripts/migrate-clickhouse b/backend/scripts/migrate-clickhouse new file mode 100755 index 00000000..e875b907 --- /dev/null +++ b/backend/scripts/migrate-clickhouse @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -eo pipefail + +exec migrate \ + -database "clickhouse://${CLICKHOUSE_HOST}:${CLICKHOUSE_TCP_PORT}?username=${CLICKHOUSE_USER}&password=${CLICKHOUSE_PASSWORD}&database=${CLICKHOUSE_DATABASE}&x-multi-statement=true" \ + -source "file:///poeticmetric/migrations/clickhouse" \ + "$@" diff --git a/backend/scripts/migrate-postgres b/backend/scripts/migrate-postgres new file mode 100755 index 00000000..a1af7d19 --- /dev/null +++ b/backend/scripts/migrate-postgres @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -eo pipefail + +exec migrate \ + -database "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DATABASE}?sslmode=disable" \ + -source "file:///poeticmetric/migrations/postgres" \ + "$@" diff --git a/frontend/@types/site.d.ts b/frontend/@types/site.d.ts index 1e7ea750..e3f2c123 100644 --- a/frontend/@types/site.d.ts +++ b/frontend/@types/site.d.ts @@ -1,6 +1,7 @@ type Site = { createdAt: string; domain: string; + hasEvents: boolean; id: number; isPublic: boolean; name: string; diff --git a/frontend/components/Billing/index.tsx b/frontend/components/Billing/index.tsx index 65f992fc..d339879e 100644 --- a/frontend/components/Billing/index.tsx +++ b/frontend/components/Billing/index.tsx @@ -2,7 +2,7 @@ import dayjs from "dayjs"; import React, { useContext, useMemo, useState } from "react"; import { Container } from "react-bootstrap"; import { Breadcrumb, Layout, Plans, Title } from ".."; -import { AuthAndApiContext, PlansContext, PlansContextValue, PlansContextState } from "../../contexts"; +import { AuthAndApiContext, PlansContext, PlansContextState, PlansContextValue } from "../../contexts"; import { BillingPortalButton } from "./BillingPortalButton"; export function Billing() { diff --git a/frontend/components/Empty/index.tsx b/frontend/components/Empty/index.tsx new file mode 100644 index 00000000..186f9039 --- /dev/null +++ b/frontend/components/Empty/index.tsx @@ -0,0 +1,22 @@ +import classNames from "classnames"; +import React from "react"; + +export type EmptyProps = Overwrite, "children">, { + description: React.ReactNode; + title: React.ReactNode; +}>; + +export function Empty({ className, description, title, ...props }: EmptyProps) { + return ( +
+ + +

{title}

+ +
{description}
+
+ ); +} diff --git a/frontend/components/Markdown/index.tsx b/frontend/components/Markdown/index.tsx index 66e15244..d20e3445 100644 --- a/frontend/components/Markdown/index.tsx +++ b/frontend/components/Markdown/index.tsx @@ -1,5 +1,7 @@ -import React from "react"; +import classNames from "classnames"; +import hljs from "highlight.js"; import BaseMarkdown from "markdown-to-jsx"; +import React, { useEffect, useRef } from "react"; import styles from "./Markdown.module.scss"; export type MarkdownProps = { @@ -8,7 +10,25 @@ export type MarkdownProps = { }; export function Markdown({ className, ...props }: MarkdownProps) { + const div = useRef(null); + const previousChildren = useRef(""); + + useEffect(() => { + if (div.current !== null && props.children !== previousChildren.current) { + div.current.querySelectorAll("pre code").forEach((e) => hljs.highlightElement(e)); + + previousChildren.current = props.children; + } + }, [props.children]); + return ( - +
+ +
); } diff --git a/frontend/components/SiteForm/index.tsx b/frontend/components/SiteForm/index.tsx index c8b0aedb..b2425fbf 100644 --- a/frontend/components/SiteForm/index.tsx +++ b/frontend/components/SiteForm/index.tsx @@ -38,11 +38,11 @@ export function SiteForm() { if (response.ok) { addToast({ - body: id === undefined ? "Site is created." : "Site is updated.", + body: id === undefined ? "Site is created. Please add the PoeticMetric tracking script to your site." : "Site is updated.", variant: "success", }); - await router.push("/sites"); + await router.push(id === undefined ? `/sites/reports?id=${responseJson.id}` : "/sites"); } else { setErrors(responseJson); } diff --git a/frontend/components/SiteReports/index.tsx b/frontend/components/SiteReports/index.tsx index d4a9b876..411bb96b 100644 --- a/frontend/components/SiteReports/index.tsx +++ b/frontend/components/SiteReports/index.tsx @@ -64,20 +64,4 @@ export function SiteReports({ site }: SiteReportsProps) { ); - - // return ( - // - // {site === undefined ? "..." : `Reports - ${site.name}`} - // - // - // {site === undefined ? ( - //
- // - //
- // ) : ( - // - // )} - //
- //
- // ); } diff --git a/frontend/components/Sites/index.tsx b/frontend/components/Sites/index.tsx index 36bd05bd..ffc29b06 100644 --- a/frontend/components/Sites/index.tsx +++ b/frontend/components/Sites/index.tsx @@ -1,6 +1,6 @@ import { useMemo } from "react"; import { Col, Container, Row, Spinner } from "react-bootstrap"; -import { Layout, Title } from ".."; +import { Empty, Layout, Title } from ".."; import { useSites } from "../../hooks"; import { DeleteModal } from "./DeleteModal"; import { Header } from "./Header"; @@ -16,6 +16,15 @@ export function Sites() { ); } + if (sites.length === 0) { + return ( + + ); + } + return ( {sites.map((s) => ( diff --git a/frontend/components/SyntaxHighlighter/index.tsx b/frontend/components/SyntaxHighlighter/index.tsx new file mode 100644 index 00000000..8fb9ed97 --- /dev/null +++ b/frontend/components/SyntaxHighlighter/index.tsx @@ -0,0 +1,25 @@ +import hljs from "highlight.js"; +import React, { useEffect, useRef } from "react"; + +export type SyntaxHighlighterProps = Overwrite, "children">, { + code: string; +}>; + +export function SyntaxHighlighter({ code, ...props }: SyntaxHighlighterProps) { + const previousCode = useRef(""); + const codeRef = useRef(null); + + useEffect(() => { + if (codeRef.current !== null && code !== previousCode.current) { + hljs.highlightElement(codeRef.current); + + previousCode.current = code; + } + }, [code]); + + return ( +
+      {code}
+    
+ ); +} diff --git a/frontend/components/index.ts b/frontend/components/index.ts index d897f815..dec1b083 100644 --- a/frontend/components/index.ts +++ b/frontend/components/index.ts @@ -11,6 +11,7 @@ export * from "./ChartTooltip"; export * from "./Description"; export * from "./DocsArticle"; export * from "./EmailAddressVerification"; +export * from "./Empty"; export * from "./FavIcon"; export * from "./Home"; export * from "./Layout"; @@ -35,6 +36,7 @@ export * from "./SiteForm"; export * from "./SiteReports"; export * from "./Sites"; export * from "./SwrConfig"; +export * from "./SyntaxHighlighter"; export * from "./Team"; export * from "./TeamMemberForm"; export * from "./TermsOfService"; diff --git a/frontend/docs/websites/2_adding-the-script-to-your-website.md b/frontend/docs/websites/2_adding-the-script-to-your-website.md index d0fccd25..23f54132 100644 --- a/frontend/docs/websites/2_adding-the-script-to-your-website.md +++ b/frontend/docs/websites/2_adding-the-script-to-your-website.md @@ -4,8 +4,8 @@ title: Adding the script to your website To start tracking your website's traffic, you need to update the HTML code of your website and add the PoeticMetric tracking script code to the header section. Put the script between the `` and `` tags. -
- +
+ If you are not the person managing the website's code, and there is someone else who deals with it, you can share the link to this page.
diff --git a/frontend/package.json b/frontend/package.json index 8f6dd9ef..e1876618 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "bootstrap-icons": "^1.9.1", "chroma-js": "^2.4.2", "classnames": "^2.3.2", + "copy-to-clipboard": "^3.3.3", "cross-env": "^7.0.3", "d3-array": "^3.2.0", "d3-scale": "^4.0.2", diff --git a/frontend/pages/_app.tsx b/frontend/pages/_app.tsx index 1267cbf0..bdbcb23c 100644 --- a/frontend/pages/_app.tsx +++ b/frontend/pages/_app.tsx @@ -6,7 +6,6 @@ import localeData from "dayjs/plugin/localeData"; import localizedFormat from "dayjs/plugin/localizedFormat"; import relativeTime from "dayjs/plugin/relativeTime"; import updateLocale from "dayjs/plugin/updateLocale"; -import hljs from "highlight.js"; import "highlight.js/styles/stackoverflow-light.css"; import { AppProps } from "next/app"; import React, { useEffect } from "react"; @@ -27,12 +26,6 @@ export default function App({ Component, pageProps }: AppProps) { registerLocale("en-GB", enGb); }, []); - useEffect(() => { - if (document.querySelectorAll("pre").length > 0) { - hljs.highlightAll(); - } - }); - return ( diff --git a/frontend/pages/_document.tsx b/frontend/pages/_document.tsx index b23b5cc9..6121a408 100644 --- a/frontend/pages/_document.tsx +++ b/frontend/pages/_document.tsx @@ -1,4 +1,4 @@ -import { Html, Head, Main, NextScript } from "next/document"; +import { Head, Html, Main, NextScript } from "next/document"; import Script from "next/script"; import React from "react"; diff --git a/frontend/pages/sites/reports.tsx b/frontend/pages/sites/reports.tsx index 8123a490..9595d895 100644 --- a/frontend/pages/sites/reports.tsx +++ b/frontend/pages/sites/reports.tsx @@ -1,7 +1,9 @@ +import copy from "copy-to-clipboard"; import { useRouter } from "next/router"; -import React, { useContext, useEffect } from "react"; -import { Container, Spinner } from "react-bootstrap"; -import { Breadcrumb, Layout, SiteReports, withAuth } from "../../components"; +import React, { useCallback, useContext, useEffect, useMemo } from "react"; +import { Button, Container, OverlayTrigger, Spinner, Tooltip } from "react-bootstrap"; +import { mutate } from "swr"; +import { Breadcrumb, Layout, SiteReports, SyntaxHighlighter, Title, withAuth } from "../../components"; import { ToastsContext } from "../../contexts"; import { useQueryParameter, useSite } from "../../hooks"; @@ -9,7 +11,19 @@ function Reports() { const router = useRouter(); const { addToast } = useContext(ToastsContext); const { hasError: hasSiteIdError, value: siteId } = useQueryParameter("id", "number"); - const { data: site, error: siteError } = useSite(siteId); + const { data: site, error: siteError, isValidating: isSiteValidating } = useSite(siteId); + + const scriptCode = useMemo(() => { + const src = `${process.env.NEXT_PUBLIC_HOSTED === "true" ? window.poeticMetric?.frontendBaseUrl : window.poeticMetric?.restApiBaseUrl}/pm.js`; + + return ``; + }, []); + + const copyScriptCodeToClipboard = useCallback(() => { + copy(scriptCode); + + addToast({ body: "Script code is copied to clipboard.", variant: "success" }); + }, [addToast, scriptCode]); useEffect(() => { if (hasSiteIdError || siteError !== undefined) { @@ -21,6 +35,8 @@ function Reports() { return ( + {site === undefined ? "Site reports" : `Reports for ${site.name}`} + {site === undefined ? ( @@ -28,7 +44,53 @@ function Reports() { <> - + {!site.hasEvents ? ( +
+

There are no events registered from this site, yet...

+ +
+ Please add the PoeticMetric tracking script to your site: +
+ +
+ + + + Click to copy + + )} + placement="bottom" + trigger={["focus", "hover"]} + > + + +
+ + {process.env.NEXT_PUBLIC_HOSTED === "true" ? ( +
+ {"Please see "} + docs + {" if you need help about adding the script to your website."} +
+ ) : null} + + +
+ ) : ( + + )} )}
diff --git a/frontend/styles/base.scss b/frontend/styles/base.scss index 220b1fa2..3cbba1a9 100644 --- a/frontend/styles/base.scss +++ b/frontend/styles/base.scss @@ -110,6 +110,13 @@ $utilities: map-merge($utilities, ( property: height, values: $fixed-sizes, ), + "icon-size": ( + class: bis, + property: font-size, + values: ( + 5rem: 5rem, + ), + ), "max-height": map-merge(map-get($utilities, "max-height"), ( values: map-merge(map-get(map-get($utilities, "max-height"), "values"), ( 50vh: 50vh, @@ -156,6 +163,24 @@ $utilities: map-merge($utilities, ( null: var(--#{$prefix}border-radius), ), ), + "rounded-top-end": ( + class: rounded-top-end, + property: border-top-right-radius, + responsive: true, + values: ( + 0: 0, + null: var(--#{$prefix}border-radius), + ), + ), + "rounded-top-start": ( + class: rounded-top-start, + property: border-top-left-radius, + responsive: true, + values: ( + 0: 0, + null: var(--#{$prefix}border-radius), + ), + ), "start": map-merge(map-get($utilities, "start"), ( responsive: true, )), diff --git a/frontend/yarn.lock b/frontend/yarn.lock index c5b3eced..6d6b4283 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2424,7 +2424,7 @@ cookie@^0.4.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== -copy-to-clipboard@^3.3.1: +copy-to-clipboard@^3.3.1, copy-to-clipboard@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==