Skip to content

Commit

Permalink
Merge pull request #1 from avito-tech/html_report
Browse files Browse the repository at this point in the history
feat: Add html report support
  • Loading branch information
nbob authored Feb 8, 2024
2 parents 9144383 + 6ef93d8 commit 9f4b9b2
Show file tree
Hide file tree
Showing 12 changed files with 522 additions and 122 deletions.
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Avito Pixel — это продукт для учёта количества п

Для запуска выполните команду
```
docker compose up
docker compose up --build
```

Команда запустит clickhouse и сервис для сбора статистики
Expand All @@ -55,14 +55,10 @@ curl -X POST http://localhost:3000/hit/ \
```

Можно выполнить данный запрос несколько раз, затем проверить, что данные сохранились в базу и готовы к выдаче.
Перед выполнением запроса измените поля `from` и `to` на актуальные.
```
curl --location -X POST 'http://localhost:3000/report/json/' \
--header 'Content-Type: application/json' \
--data-raw '{
"metric": "visitors",
"interval": 1,
"from": "2023-12-02",
"to": "2023-12-06"
}'
curl --location -X GET 'http://localhost:3000/report/json/?metric=visitors&from=2024-01-01&to=2024-01-31&interval=1'
```
или откройте страницу http://localhost:3000/report/html/?metric=visitors&from=2023-12-02&to=2024-12-06&interval=1

Перед выполнением запроса измените поля `from` и `to` на актуальные.

3 changes: 1 addition & 2 deletions db/changelog/master/1-init-20-11-2023.sql
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ CREATE TABLE IF NOT EXISTS analytics.visitors_1_day
totalHits UInt64
)
ENGINE = SummingMergeTree
ORDER BY (eventTime, sessionID)
TTL eventTime + INTERVAL 7 DAY;
ORDER BY (eventTime, sessionID);


CREATE MATERIALIZED VIEW IF NOT EXISTS analytics.visitors_1_day_mv
Expand Down
5 changes: 1 addition & 4 deletions example/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"context"
"fmt"
"net/http"

"github.com/avito-tech/avito-pixel/example/service/extras"
Expand All @@ -14,7 +13,6 @@ import (
)

func main() {
fmt.Println("1111")
logger := extras.NewLogger()
err := godotenv.Load()
if err != nil {
Expand All @@ -36,7 +34,6 @@ func main() {
mux.Handle("/hit/", hitHandler.Build())
mux.Handle("/report/csv/", reportHandler.CsvBuild())
mux.Handle("/report/json/", reportHandler.JsonBuild())
mux.Handle("/report/html/", reportHandler.HtmlBuild())
http.ListenAndServe(":3000", mux)

fmt.Println("azazax")
}
2 changes: 1 addition & 1 deletion lib/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type Clickhouse struct {
MaxIdleConns int `env:"CLICKHOUSE_MAX_IDLE_CONNS,required"`
ConnLifetime time.Duration `env:"CLICKHOUSE_CONN_LIFETIME,required"`
MaxThreads int `env:"CLICKHOUSE_MAX_THREADS,required"`
SkipVerify bool `env:"CLICKHOUSE_SKIP_VERIFY"`
SkipVerify *bool `env:"CLICKHOUSE_SKIP_VERIFY"`
CaCertFile string `env:"CA_CERT_FILE"`
Cert string `env:"CERT_FILE"`
CertKey string `env:"KEY_FILE"`
Expand Down
246 changes: 246 additions & 0 deletions lib/report/assets/report.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AvitoPixel отчет</title>
</head>
<style>
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}


:root {
--height-header: 80px;
--width-container: 1316px;
}

body {
font-family: 'Manrope', Arial, Helvetica, sans-serif;
}
header {
position: sticky;
width: 100%;
top: 0;
height: var(--height-header);
}
.header__container {
width: 1316px;
margin: 0 auto;
padding: 24px 8px;
}
h1 {
font-size: 32px;
font-weight: 700;
}

h2 {
font-size: 26px;
line-height: 30px;
font-weight: 700;
}

h3 {
font-size: 21px;
line-height: 26px;
font-weight: 700;
}


main {
display: block;
height: calc(100vh - var(--height-header));
}

.main__container {
max-width: var(--width-container);
margin: 0 auto;
padding: 10px 8px;
}

.period__main {
padding: 12px 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr) );
gap: 10px;
}

.month__main {
padding-top: 8px;
}

.table {
width: 100%;
margin-bottom: 20px;
border: 1px solid #dddddd;
border-collapse: collapse;
font-size: 14px;
}

.table th {
font-weight: bold;
padding: 5px;
background: #efefef;
border: 1px solid #dddddd;
}

.table td {
border: 1px solid #dddddd;
padding: 1.6px;
text-align: center;
}
</style>

<script>
window['data'] = { report: JSON.parse({{ .PayloadString }}) };
</script>

<body>
<header>
<div class="header__container">
<h1>AvitoPixel отчет</h1>
</div>
</header>
<main>
<div class="main__container">
<div class="period">
<div class="period__header">
<h2>Всего событий: <span id="hit-total"></span></h2>
</div>
<div class="period__main" id="period">

</div>
</div>
</div>
</main>
<footer>

</footer>
</body>

<script>
function setInfo(id, value) {
const element = document.getElementById(id);
if (element) {
element.innerText = value;
}
}
const report = window['data'].report;

setInfo("hit-total", report.total);

const period = document.getElementById('period');

function createMonthTable(rootMain, item) {
const table = document.createElement('table');
table.className = 'table';

const thead = document.createElement('thead');
table.appendChild(thead);

const numberTitle = document.createElement('th');
numberTitle.innerText = 'Число';
thead.appendChild(numberTitle);

const valueTitle = document.createElement('th');
valueTitle.innerText = 'Кол-во';
thead.appendChild(valueTitle);

const tbody = document.createElement('tbody');
table.appendChild(tbody);

item.days.forEach((el, index) => {
const row = document.createElement('tr');

tbody.appendChild(row);

const cellNumber = document.createElement('td');
cellNumber.innerText = el.date;
row.appendChild(cellNumber);

const cellValue = document.createElement('td');
cellValue.innerText = el.total;
row.appendChild(cellValue);
})

rootMain.appendChild(table);
}

function createMonthMain(rootMonth, item) {
const main = document.createElement('div');
main.className = 'month__main';

rootMonth.appendChild(main);

createMonthTable(main, item);
}

function createMonthTitle(rootHeaderMonth, item) {
const titleMonth = document.createElement('h3');
titleMonth.innerText = `${item.month} ${item.year}: ${item.total}`;
rootHeaderMonth.appendChild(titleMonth);
}

function createMonthHeader(rootMonth, item) {
const header = document.createElement('div');
header.className = "month__header";
rootMonth.appendChild(header);

createMonthTitle(header, item);
}

function createMonth(rootPeriod, item) {
const month = document.createElement('div');
month.className = "month";
rootPeriod.appendChild(month);

createMonthHeader(month, item);
createMonthMain(month, item);
}

report.months.forEach((item, index) => {
createMonth(period, item);
});
</script>
</html>
53 changes: 53 additions & 0 deletions lib/report/csv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package report

import "net/http"

// TODO: fix duplicating request parsing and validation
func (h *Handler) CsvBuild() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if r.Method != http.MethodGet {
err := ResponseFail(w, 404, "Route not found")
if err != nil {
h.logger.Error(ctx, err)
}
return
}
reportSettings, err := parseReportSettingsFromQueryParams(r)
if err != nil {
h.logger.Error(ctx, err)
err = ResponseFail(w, 400, "Bad request: could not parse request body")
if err != nil {
h.logger.Error(ctx, err)
}
return
}

metrics, err := h.storage.GetReport(ctx, reportSettings)
if err != nil {
h.logger.Error(ctx, err)
err = ResponseFail(w, 500, "Internal server error: could not retrieve metrics")
if err != nil {
h.logger.Error(ctx, err)
}
return
}
err = CsvResponseOk(w, metrics)
if err != nil {
h.logger.Error(ctx, err)
}
})
}

func CsvResponseOk(w http.ResponseWriter, metrics Metrics) error {
raw, err := ToCsv(metrics)
if err != nil {
return err
}
w.Header().Set("Content-Type", "text/csv")
_, err = w.Write(raw)
if err != nil {
return err
}
return nil
}
Loading

0 comments on commit 9f4b9b2

Please sign in to comment.