Файл: Разработка системы сбора и анализа данных о пользовательском поведении.docx
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.10.2023
Просмотров: 164
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Глава 1. Теоретическое обоснование и актуальность проблематики
Анализ современных систем метрик активности
Глава 2. Исследование проблемы
2.1. Анализ современных систем отслеживания активности пользователя
2.2. Анализ возможных решений для улучшения отслеживания активности пользователей
2.3. Обоснование выбранного решения путем анкетирования
3.1. Общее описание архитектуры системы
3.3. Диаграммы сценариев и последовательностей
3.5. Последовательность интеграции модуля в клиентскую часть пользовательского приложения
3.6. Последовательность интеграции серверной части системы
Список литературы
-
Сенге П. Пятая дисциплина. Искусство и практика обучающейся организации: [монография] – Москва: Питер, 2006. -660 с. -
Pearl J., Mackenzie D. Почему? Новая наука о причинно-следственной связи: [монография] – Москва: Дело, 2018. -432 с. -
Медоуз Д. Х. Азбука системного мышления: [монография] – Москва: Альпина Паблишер, 2008. -320 с. -
Канеман Д. Думай медленно… решай быстро: [монография] – Москва: Манн, Иванов и Фербер, 2011. -542 с. -
Хосе П. Э. Занимаясь статистическим опосредованием и модерацией: [монография] – Москва: Кодекс, 2013. -336 с. -
Гленнерстер Р., Такавараша К. Проведение рандомизированных оцениваний: практическое руководство: [монография] – Москва: Новое знание, 2013. -480 с. -
Гербер А. С., Грин Д. П. Полевые эксперименты: дизайн, анализ и интерпретация: [монография] – Москва: Дело, 2012. -492 с. -
Funder D. C. Головоломка личности: [монография] – Москва: Когито-Центр, 2016. -664 с. -
Эйял Н. Зацепленный: как создавать продукты, формирующие привычки: [монография] – Москва: Манн, Иванов и Фербер, 2014. -256 с. -
Коэн Дж. Статистический анализ мощности для бихевиористики: [монография] – Москва: Кодекс, 2013. -567 с. -
Ангрист Д. Д., Пишке Й.-Ш. В основном безобидная эконометрия: компаньон Эмпирика – Москва: Издательство "Дело", 2009. - 373 с. -
Хавербеке М. Выразительный JavaScript. Современное веб-программирование – Москва: Издательство "Питер", 2022. - 480 с. -
Мартин Р. Чистая архитектура. Искусство разработки программного обеспечения – Москва: Издательство "БХВ-Петербург", 2022. - 352 с. -
Чиннатхамби К. JavaScript с нуля – Москва: Издательство "Питер", 2022. - 400 с. -
Грин Дж., Стиллмен Э. Head First. Изучаем C#. 4-е издание – Москва: Издательство "Питер", 2022. - 768 с. -
Прайс М. C# 8 и .NET Core. Разработка и оптимизация – Москва: Издательство "БХВ-Петербург", 2021. - 816 с. -
Фримен А. ASP.NET Core MVC с примерами на C# для профессионалов – Москва: Издательство "Вильямс", 2017. - 992 с. -
Стефанов С. React. Быстрый старт, 2-е издание – Москва: Издательство "ДМК Пресс", 2023. - 304 с. -
Уилкокс Р. Р. Основы современных статистических методов: существенное повышение мощности и точности. 2-е издание – Москва: Издательство "ИНФРА-М", 2010. - 249 с. -
Вандервил Т. Объяснение в причинно-следственном выводе: методы опосредования и взаимодействия – Москва: Издательство "Логос", 2015. - 728 с.
Приложение 1
uamodule.js:
const SERVER_API = 'http://localhost:5252/api/eventdata';
function getUserInfo() {
const userAgent = navigator.userAgent;
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
const isMobile = /Mobi/i.test(userAgent);
const isDesktop = !isMobile;
const isiOS = /iPhone|iPad|iPod/i.test(userAgent);
const isAndroid = /Android/i.test(userAgent);
let browser = 'Unknown';
if (userAgent.includes('Firefox')) {
browser = 'Firefox';
} else if (userAgent.includes('Chrome')) {
browser = 'Chrome';
} else if (userAgent.includes('Safari')) {
browser = 'Safari';
} else if (userAgent.includes('Edge')) {
browser = 'Edge';
} else if (userAgent.includes('Opera') || userAgent.includes('OPR')) {
browser = 'Opera';
} else if (userAgent.includes('Trident') || userAgent.includes('MSIE')) {
browser = 'Internet Explorer';
}
let os = 'Unknown';
if (userAgent.includes('Windows')) {
os = 'Windows';
} else if (userAgent.includes('Mac OS')) {
os = 'Mac OS';
} else if (userAgent.includes('Linux')) {
os = 'Linux';
} else if (isiOS) {
os = 'iOS';
} else if (isAndroid) {
os = 'Android';
}
const userInfo = {
userAgent: userAgent,
viewportWidth: viewportWidth,
viewportHeight: viewportHeight,
isMobile: isMobile,
isDesktop: isDesktop,
isiOS: isiOS,
isAndroid: isAndroid,
browser: browser,
os: os
};
return userInfo;
}
function send(userName="guest", category="", action="", label="", value="") {
const payload = {
eventUserName: userName,
eventCategory: category,
eventAction: action,
eventLabel: label,
eventTimeStamp: new Date(Date.now()).toISOString().slice(0, 19),
eventUrl: window.location.href,
eventValue: value,
UserInformation: getUserInfo()
};
fetch(SERVER_API, {
method: 'POST',
mode: 'cors', // no-cors, *cors, same-origin
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
}).then(response => {
if (!response.ok) throw new Error('Network response was not ok');
}).catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
}
function init() {
return { send };
}
window.send = send;
Приложение 2
EventDataModel.cs:
namespace UserActivityService.Models;
public class EventData
{
public string EventAction { get; set; }
public string EventCategory { get; set; }
public string EventLabel { get; set; }
public DateTime EventTimeStamp { get; set; }
public string EventUrl { get; set; }
public string EventUserName { get; set; }
public string EventValue { get; set; }
public UserInfo UserInformation { get; set; }
}
UserInfo.cs
namespace UserActivityService.Models;
public class UserInfo
{
public string UserAgent { get; set; }
public int ViewportWidth { get; set; }
public int ViewportHeight { get; set; }
public bool IsMobile { get; set; }
public bool IsDesktop { get; set; }
public bool IsiOS { get; set; }
public bool IsAndroid { get; set; }
public string Browser { get; set; }
public string OS { get; set; }
}
EventService.cs:
using UserActivityService.DTO;
namespace UserActivityService.Models;
public interface IEventService
{
Task SaveEventDataAsync(EventData eventData);
Task
- > GetDeviceUsageDataAsync();
Task
- > GetEventActionDataAsync();
Task
- > GetEventCategoryDataAsync();
Task
- > GetUserActivityDataAsync();
Task
- > GetUserJourneyDataAsync();
}
public class EventService : IEventService
{
private readonly string _filePath;
public EventService(string filePath)
{
_filePath = filePath;
}
public async Task SaveEventDataAsync(EventData eventData)
{
var eventDataJson = System.Text.Json.JsonSerializer.Serialize(eventData);
await System.IO.File.AppendAllTextAsync(_filePath, Environment.NewLine + eventDataJson);
}
public async Task
- > LoadEventDataAsync()
{
var lines = await System.IO.File.ReadAllLinesAsync(_filePath);
var eventDataList = new List
foreach (var line in lines)
{
var eventData = System.Text.Json.JsonSerializer.Deserialize
eventDataList.Add(eventData);
}
return eventDataList;
}
public async Task
- > GetDeviceUsageDataAsync()
{
var events = await LoadEventDataAsync();
var deviceUsageData = events
.GroupBy(e => new { e.UserInformation.OS, e.UserInformation.Browser, e.UserInformation.IsMobile, e.UserInformation.IsDesktop })
.Select(group => new DeviceUsageDto
{
OS = group.Key.OS,
Browser = group.Key.Browser,
IsMobile = group.Key.IsMobile,
IsDesktop = group.Key.IsDesktop,
Count = group.Count()
})
.ToList();
return deviceUsageData;
}
public async Task
- > GetEventActionDataAsync()
{
var events = await LoadEventDataAsync();
var actionData = events
.GroupBy(e => e.EventAction)
.Select(group => new EventActionDto
{
Action = group.Key,
Count = group.Count()
})
.ToList();
return actionData;
}
public async Task
- > GetEventCategoryDataAsync()
{
var events = await LoadEventDataAsync();
var categoryData = events
.GroupBy(e => e.EventCategory)
.Select(group => new EventCategoryDto
{
Category = group.Key,
Count = group.Count()
})
.ToList();
return categoryData;
}
public async Task
- > GetUserActivityDataAsync()
{
var events = await LoadEventDataAsync();
var userActivityData = events
.Select(e => new UserActivityDto
{
UserName = e.EventUserName,
TimeStamp = e.EventTimeStamp,
Action = e.EventAction,
Category = e.EventCategory
})
.OrderBy(d => d.TimeStamp)
.ToList();
return userActivityData;
}
public async Task
- > GetUserJourneyDataAsync()
{
var events = await LoadEventDataAsync();
var userJourneyData = events
.GroupBy(e => e.EventUserName)
.Select(group => new UserJourneyDto
{
UserName = group.Key,
UserJourney = group
.Select(e => new EventDataDto
{
EventAction = e.EventAction,
EventCategory = e.EventCategory,
EventUrl = e.EventUrl,
EventTimeStamp = e.EventTimeStamp,
})
.OrderBy(d => d.EventTimeStamp)
.ToList()
})
.ToList();
return userJourneyData;
}
}
DeviceUsageDto.cs:
namespace UserActivityService.DTO;
public class DeviceUsageDto
{
public string OS { get; set; }
public string Browser { get; set; }
public bool IsMobile { get; set; }
public bool IsDesktop { get; set; }
public int Count { get; set; }
}
EventActionDto.cs:
namespace UserActivityService.DTO;
public class EventActionDto
{
public string Action { get; set; }
public int Count { get; set; }
}
EventCategoryDto.cs:
namespace UserActivityService.DTO;
public class EventCategoryDto
{
public string Category { get; set; }
public int Count { get; set; }
}
UserActivityDto.cs:
namespace UserActivityService.DTO;
public class UserActivityDto
{
public string UserName { get; set; }
public DateTime TimeStamp { get; set; }
public string Action { get; set; }
public string Category { get; set; }
}
UserJourneyDto.cs:
namespace UserActivityService.DTO;
public class UserJourneyDto
{
public string UserName { get; set; }
public List
}
public class EventDataDto
{
public string EventAction { get; set; }
public string EventCategory { get; set; }
public string EventUrl { get; set; }
public DateTime EventTimeStamp { get; set; }
}
DeviceUsageController.cs:
using Microsoft.AspNetCore.Mvc;
using UserActivityService.DTO;
namespace UserActivityService.Controllers;
[ApiController]
[Route("api/[controller]")]
public class DeviceUsageController : ControllerBase
{
private readonly IEventService _eventService;
public DeviceUsageController(IEventService eventService)
{
_eventService = eventService;
}
[HttpGet]
public async Task
{
var deviceUsages = await _eventService.GetDeviceUsageDataAsync();
if (deviceUsages == null)
{
return NotFound();
}
return deviceUsages;
}
}
EventActionController.cs:
using Microsoft.AspNetCore.Mvc;
using UserActivityService.DTO;
namespace UserActivityService.Controllers;
[ApiController]
[Route("api/[controller]")]
public class EventActionController : ControllerBase
{
private readonly IEventService _eventService;
public EventActionController(IEventService eventService)
{
_eventService = eventService;
}
[HttpGet]
public async Task
{
var eventActions = await _eventService.GetEventActionDataAsync();
if (eventActions == null)
{
return NotFound();
}
return eventActions;
}
}
EventCategoryController.cs:
using Microsoft.AspNetCore.Mvc;
using UserActivityService.DTO;
namespace UserActivityService.Controllers;
[ApiController]
[Route("api/[controller]")]
public class EventCategoryController : ControllerBase
{
private readonly IEventService _eventService;
public EventCategoryController(IEventService eventService)
{
_eventService = eventService;
}
[HttpGet]
public async Task
{
var eventCategories = await _eventService.GetEventCategoryDataAsync();
if (eventCategories == null)
{
return NotFound();
}
return eventCategories;
}
}
EventDataController.cs:
using Microsoft.AspNetCore.Mvc;
namespace UserActivityService.Controllers;
[ApiController]
[Route("api/[controller]")]
public class EventDataController : ControllerBase
{
private readonly IEventService _eventService;
public EventDataController(IEventService eventService)
{
_eventService = eventService;
}
// POST api/eventdata
[HttpPost]
public async Task
{
if (eventData == null)
{
return BadRequest("Event data is null.");
}
try
{
await _eventService.SaveEventDataAsync(eventData);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
return Ok();
}
[HttpGet]
public async Task
{
return "yes";
}
}
UserActivityController.cs:
using Microsoft.AspNetCore.Mvc;
using UserActivityService.DTO;
namespace UserActivityService.Controllers;
[ApiController]
[Route("api/[controller]")]
public class UserActivityController : ControllerBase
{
private readonly IEventService _eventService;
public UserActivityController(IEventService eventService)
{
_eventService = eventService;
}
[HttpGet]
public async Task
{
var userActivities = await _eventService.GetUserActivityDataAsync();
if (userActivities == null)
{
return NotFound();
}
return userActivities;
}
}
UserJourneyController.cs:
using Microsoft.AspNetCore.Mvc;
using UserActivityService.DTO;
namespace UserActivityService.Controllers;
[ApiController]
[Route("api/[controller]")]
public class UserJourneyController : ControllerBase
{
private readonly IEventService _eventService;
public UserJourneyController(IEventService eventService)
{
_eventService = eventService;
}
[HttpGet]
public async Task
{
var userJourneys = await _eventService.GetUserJourneyDataAsync();
if (userJourneys == null)
{
return NotFound();
}
return userJourneys;
}
}
Program.cs:
using Microsoft.AspNetCore.Cors.Infrastructure;
using UserActivityService;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Add services to the container.
string filename = builder.Configuration.GetValue
builder.Services.AddSingleton
// Add CORS policy
var corsPolicy = new CorsPolicyBuilder()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()
.Build();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", corsPolicy);
});
var app = builder.Build();
app.UseDefaultFiles();
app.UseStaticFiles();
// Enable CORS with the policy "AllowAll"
app.UseCors("AllowAll");
// Configure endpoints
app.UseRouting();
app.MapControllers();
app.Run();
Приложение 3
Index.html:
`, or the closest parent with a set `text-align`. */
text-align: inherit;
}
/*
Forms
*/
label {
/* Allow labels to use `margin` for spacing. */
display: inline-block;
margin-bottom: .5rem;
}
/*
Remove the default `border-radius` that macOS Chrome adds.
Details at https://github.com/twbs/bootstrap/issues/24093
*/
button {
border-radius: 0;
}
/*
Work around a Firefox/IE bug where the transparent `button` background results in a loss of the default `button` focus styles.
Credit: https://github.com/suitcss/base/
*/
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
/* Remove the margin in Firefox and Safari */
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
/* Show the overflow in Edge */
}
button,
select {
text-transform: none;
/* Remove the inheritance of text transform in Firefox */
}
/*
1. Prevent a WebKit bug where (2) destroys native `audio` and `video` controls in Android 4.
2. Correct the inability to style clickable types in iOS and Safari.
*/
button,
html [type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/*
Remove inner border and padding from Firefox, but don't restore the outline like Normalize.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
/* 1. Add the correct box sizing in IE 10- */
padding: 0;
/* 2. Remove the padding in IE 10- */
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
/*
Remove the default appearance of temporal inputs to avoid a Mobile Safari bug where setting a custom line-height prevents text from being vertically centered within the input.
See https://bugs.webkit.org/show_bug.cgi?id=139848
and https://github.com/twbs/bootstrap/issues/11266
*/
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
/* Remove the default vertical scrollbar in IE. */
/* Textareas should really only resize vertically so they don't break their (horizontal) containers. */
resize: vertical;
}
fieldset {
/*
Browsers set a default `min-width: min-content;` on fieldsets, unlike e.g. `
`s, which have `min-width: 0;` by default. So we reset that to ensure fieldsets behave more like a standard block element.
See https://github.com/twbs/bootstrap/issues/12359
and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements
*/
min-width: 0;
/* Reset the default outline behavior of fieldsets so they don't affect page layout. */
padding: 0;
margin: 0;
border: 0;
}
/*
1. Correct the text wrapping in Edge and IE.
2. Correct the color inheritance from `fieldset` elements in IE.
*/
legend {
display: block;
width: 100%;
max-width: 100%;
/* 1 */
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
/* 2 */
white-space: normal;
/* 1 */
}
progress {
vertical-align: baseline;
/* Add the correct vertical alignment in Chrome, Firefox, and Opera. */
}
/*
Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
/*
This overrides the extra rounded corners on search inputs in iOS so that our `.form-control` class can properly style them. Note that this cannot simply be added to `.form-control` as it's not specific enough.
For details, see https://github.com/twbs/bootstrap/issues/11586.
*/
outline-offset: -2px;
-webkit-appearance: none;
}
/*
Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
/*
Correct element displays
*/
output {
display: inline-block;
}
summary {
display: list-item;
/* Add the correct display in all browsers */
}
template {
display: none;
/* Add the correct display in IE */
}
/*
Always hide an element with the `hidden` HTML attribute (from PureCSS).
Needed for proper display in IE 10-.
*/
[hidden] {
display: none !important;
}
/* -------------------------------------------------------------------------------- */
html,
body,
.application {
width: 100%;
height: 100%;
}
body {
font-family: monospace;
font-weight: normal;
font-size: 14px;
}
.application {
overflow: hidden;
display: flex;
flex-direction: column;
padding: 10px 10px;
}
.application__header {
display: flex;
width: 100%;
flex-direction: row;
justify-content: start;
align-content: center;
}
.application__header > div.header {
font-weight: bold;
font-size: 24px;
}
.application__content {
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
place-content: start center;
align-items: center;
justify-content: start;
}
.application__navigation {
display: flex;
width: 100%;
flex-direction: row;
justify-content: start;
align-content: center;
}
.navigation__element {
box-sizing: border-box;
text-transform: none;
scroll-margin-top: 80px;
scroll-margin-bottom: 100px;
display: inline-block;
padding: 3px 10px;
text-align: center;
text-decoration: none;
vertical-align: middle;
user-select: none;
border-radius: 3px;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
cursor: pointer;
margin: .25rem .125rem;
}
.navigation__element-inactive {
background-color: #f9f9f9;
border: 1px solid black;
color: black;
}
.navigation__element-active {
background-color: #1c68ca;
border: 1px solid #1c68ca;
color: rgb(255, 255, 255);
}
.navigation__element-inactive:hover {
background-color: #76ca1c;
border: 1px solid #1c68ca;
color: rgb(0, 0, 0);
}
.navigation__element-active:hover {
/* background-color: #f9f9f9;
border: 1px solid black;
color: black; */
}
.route {
overflow: auto;
padding: 0 5%;
display: flex;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: start;
}
/* -------------------------------------------------------------------------------- */
.journeys, .event-container {
display: flex;
flex-direction: column;
align-items: start;
justify-content: start;
width: 100%;
height: 100%;
}
.journey {
display: flex;
flex-direction: row;
justify-content: start;
align-items: start;
width: 100%;
margin: 5px;
}
.userName {
min-width: 100px;
font-size: 20px;
}
.event-container {
border: 1px solid black;
display: flex;
flex-direction: row;
overflow-x: auto;
align-items: center;
justify-content: start;
padding: 5px;
}
.events-container {
display: flex;
flex-direction: row;
overflow-x: auto;
align-items: center;
justify-content: start;
}
.event-group {
display: flex;
flex-direction: row;
align-items: center;
justify-content: start;
/*border: 1px solid black;*/
margin: 0 5px;
}
.event-name {
margin: 2px;
}
.event-body {
border: 1px solid black;
margin: 5px;
padding: 2px 2px 20px 10px;
}
.event-property {
margin: 1px;
border-bottom: 1px solid black;
display: flex;
flex-direction: column;
align-items: start;
justify-content: start;
}
eventaction.js:
import React, { useEffect, useRef } from 'react';
import * as d3 from "d3";
// {action: 'someAction6', count: 2}
function mk_chart(svg, data, w, h) {
// Declare the chart dimensions and margins.
const width = w;
const height = h;
const marginTop = 50;
const marginRight = 50;
const marginBottom = 50;
const marginLeft = 50;
// Declare the x (horizontal position) scale.
const x = d3.scaleBand()
.domain(d3.groupSort(data, ([d]) => -d.count, (d) => d.action)) // descending frequency
.range([marginLeft, width - marginRight])
.padding(0.1);
// Declare the y (vertical position) scale.
const y = d3.scaleLinear()
.domain([0, d3.max(data, (d) => d.count)])
.range([height - marginBottom, marginTop]);
// Create the SVG container.
// const svg = d3.create("svg")
svg
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
// Add a rect for each bar.
svg.append("g")
.attr("fill", "steelblue")
.selectAll()
.data(data)
.join("rect")
.attr("x", (d) => x(d.action))
.attr("y", (d) => y(d.count))
.attr("height", (d) => y(0) - y(d.count))
.attr("width", x.bandwidth());
// Add the x-axis and label.
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x).tickSizeOuter(0));
// Add the y-axis and label, and remove the domain line.
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y).tickFormat((y) => y))
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 20)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text("↑ amount (№)"));
// Return the SVG element.
return svg.node();
}
const EventAction = ({ width, height }) => {
const ref = useRef(d3.create("svg"));
const svgWidth = width - 100;
const svgHeight = height;
const SVGContainer = () =>
useEffect(() => {
fetch("/api/eventaction")
.then(response => response.json())
.then(data => {
if (data) {
let svg = d3.select("svg");
svg.selectAll("*").remove();
mk_chart(svg, data, width, height)
}
})
.catch(err => console.log(err));
}, [ref.current]);
return ( );
};
window.EventAction = EventAction;
eventcategory.js:
import React, { StrictMode } from 'react';
import { useState, useEffect, useRef } from 'react';
import * as d3 from "d3";
const Legend = ({ data, color }) => {
return (
{
data.map((item, index) => (
))
}
);
};
const EventCategory = () => {
const d3Container = useRef(null);
const width = 450;
const height = 450;
const margin = 40;
const radius = Math.min(width, height) / 2 - margin;
const legendRectSize = 18;
const legendSpacing = 4;
const [datas, setDatas] = useState([]);
const [colors, setColors] = useState({});
useEffect(() => {
fetch("/api/eventcategory")
.then(response => response.json())
.then(data => {
console.log(data)
if (data && d3Container.current) {
const svg = d3.select(d3Container.current)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
const color = d3.scaleOrdinal()
.domain(data.map(d => d.category))
.range(d3.schemeDark2);
setColors(Object.fromEntries(data.map(d => ([d.category, color(d.category)]))));
setDatas(data);
const pie = d3.pie()
.value(d => d.count)
.sort((a, b) => d3.ascending(a.category, b.category));
const arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
const data_ready = pie(data);
var u = svg.selectAll("path")
.data(data_ready);
u.enter()
.append('path')
.merge(u)
.transition()
.duration(1000)
.attr('d', arc)
.attr('fill', d => color(d.data.category))
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 1);
u.exit().remove();
// Adding labels to pie chart
u.enter()
.append("text")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) {
return d.data.count;
});
// Creating a color legend
const legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var horz = width / 2 + 2 * legendRectSize;
var vert = i * (legendRectSize + legendSpacing) - height / 2;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function (d) { return d; });
}
}, [d3Container.current])
.catch(err => console.log(err));
}, []);
return (
);
};
window.EventCategory = EventCategory;
useractivity.js:
import React, { StrictMode } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { useTable, useSortBy } from 'react-table';
const UserTable = ({ userName, data }) => {
const columns = useMemo(
() => [
{
Header: 'Category',
accessor: 'category',
},
{
Header: 'Action',
accessor: 'action',
},
{
Header: 'Time Stamp',
accessor: 'timeStamp',
},
],
[]
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({ columns, data }, useSortBy);
return (
See https://github.com/twbs/bootstrap/issues/12359
and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements
*/
min-width: 0;
/* Reset the default outline behavior of fieldsets so they don't affect page layout. */
padding: 0;
margin: 0;
border: 0;
}
/*
1. Correct the text wrapping in Edge and IE.
2. Correct the color inheritance from `fieldset` elements in IE.
*/
legend {
display: block;
width: 100%;
max-width: 100%;
/* 1 */
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
/* 2 */
white-space: normal;
/* 1 */
}
progress {
vertical-align: baseline;
/* Add the correct vertical alignment in Chrome, Firefox, and Opera. */
}
/*
Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
/*
This overrides the extra rounded corners on search inputs in iOS so that our `.form-control` class can properly style them. Note that this cannot simply be added to `.form-control` as it's not specific enough.
For details, see https://github.com/twbs/bootstrap/issues/11586.
*/
outline-offset: -2px;
-webkit-appearance: none;
}
/*
Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
/*
Correct element displays
*/
output {
display: inline-block;
}
summary {
display: list-item;
/* Add the correct display in all browsers */
}
template {
display: none;
/* Add the correct display in IE */
}
/*
Always hide an element with the `hidden` HTML attribute (from PureCSS).
Needed for proper display in IE 10-.
*/
[hidden] {
display: none !important;
}
/* -------------------------------------------------------------------------------- */
html,
body,
.application {
width: 100%;
height: 100%;
}
body {
font-family: monospace;
font-weight: normal;
font-size: 14px;
}
.application {
overflow: hidden;
display: flex;
flex-direction: column;
padding: 10px 10px;
}
.application__header {
display: flex;
width: 100%;
flex-direction: row;
justify-content: start;
align-content: center;
}
.application__header > div.header {
font-weight: bold;
font-size: 24px;
}
.application__content {
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
place-content: start center;
align-items: center;
justify-content: start;
}
.application__navigation {
display: flex;
width: 100%;
flex-direction: row;
justify-content: start;
align-content: center;
}
.navigation__element {
box-sizing: border-box;
text-transform: none;
scroll-margin-top: 80px;
scroll-margin-bottom: 100px;
display: inline-block;
padding: 3px 10px;
text-align: center;
text-decoration: none;
vertical-align: middle;
user-select: none;
border-radius: 3px;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out;
cursor: pointer;
margin: .25rem .125rem;
}
.navigation__element-inactive {
background-color: #f9f9f9;
border: 1px solid black;
color: black;
}
.navigation__element-active {
background-color: #1c68ca;
border: 1px solid #1c68ca;
color: rgb(255, 255, 255);
}
.navigation__element-inactive:hover {
background-color: #76ca1c;
border: 1px solid #1c68ca;
color: rgb(0, 0, 0);
}
.navigation__element-active:hover {
/* background-color: #f9f9f9;
border: 1px solid black;
color: black; */
}
.route {
overflow: auto;
padding: 0 5%;
display: flex;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: start;
}
/* -------------------------------------------------------------------------------- */
.journeys, .event-container {
display: flex;
flex-direction: column;
align-items: start;
justify-content: start;
width: 100%;
height: 100%;
}
.journey {
display: flex;
flex-direction: row;
justify-content: start;
align-items: start;
width: 100%;
margin: 5px;
}
.userName {
min-width: 100px;
font-size: 20px;
}
.event-container {
border: 1px solid black;
display: flex;
flex-direction: row;
overflow-x: auto;
align-items: center;
justify-content: start;
padding: 5px;
}
.events-container {
display: flex;
flex-direction: row;
overflow-x: auto;
align-items: center;
justify-content: start;
}
.event-group {
display: flex;
flex-direction: row;
align-items: center;
justify-content: start;
/*border: 1px solid black;*/
margin: 0 5px;
}
.event-name {
margin: 2px;
}
.event-body {
border: 1px solid black;
margin: 5px;
padding: 2px 2px 20px 10px;
}
.event-property {
margin: 1px;
border-bottom: 1px solid black;
display: flex;
flex-direction: column;
align-items: start;
justify-content: start;
}
eventaction.js:
import React, { useEffect, useRef } from 'react';
import * as d3 from "d3";
// {action: 'someAction6', count: 2}
function mk_chart(svg, data, w, h) {
// Declare the chart dimensions and margins.
const width = w;
const height = h;
const marginTop = 50;
const marginRight = 50;
const marginBottom = 50;
const marginLeft = 50;
// Declare the x (horizontal position) scale.
const x = d3.scaleBand()
.domain(d3.groupSort(data, ([d]) => -d.count, (d) => d.action)) // descending frequency
.range([marginLeft, width - marginRight])
.padding(0.1);
// Declare the y (vertical position) scale.
const y = d3.scaleLinear()
.domain([0, d3.max(data, (d) => d.count)])
.range([height - marginBottom, marginTop]);
// Create the SVG container.
// const svg = d3.create("svg")
svg
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
// Add a rect for each bar.
svg.append("g")
.attr("fill", "steelblue")
.selectAll()
.data(data)
.join("rect")
.attr("x", (d) => x(d.action))
.attr("y", (d) => y(d.count))
.attr("height", (d) => y(0) - y(d.count))
.attr("width", x.bandwidth());
// Add the x-axis and label.
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x).tickSizeOuter(0));
// Add the y-axis and label, and remove the domain line.
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y).tickFormat((y) => y))
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 20)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text("↑ amount (№)"));
// Return the SVG element.
return svg.node();
}
const EventAction = ({ width, height }) => {
const ref = useRef(d3.create("svg"));
const svgWidth = width - 100;
const svgHeight = height;
const SVGContainer = () =>
useEffect(() => {
fetch("/api/eventaction")
.then(response => response.json())
.then(data => {
if (data) {
let svg = d3.select("svg");
svg.selectAll("*").remove();
mk_chart(svg, data, width, height)
}
})
.catch(err => console.log(err));
}, [ref.current]);
return (
};
window.EventAction = EventAction;
eventcategory.js:
import React, { StrictMode } from 'react';
import { useState, useEffect, useRef } from 'react';
import * as d3 from "d3";
const Legend = ({ data, color }) => {
return (
{
data.map((item, index) => (
{item.category}
))
}
);
};
const EventCategory = () => {
const d3Container = useRef(null);
const width = 450;
const height = 450;
const margin = 40;
const radius = Math.min(width, height) / 2 - margin;
const legendRectSize = 18;
const legendSpacing = 4;
const [datas, setDatas] = useState([]);
const [colors, setColors] = useState({});
useEffect(() => {
fetch("/api/eventcategory")
.then(response => response.json())
.then(data => {
console.log(data)
if (data && d3Container.current) {
const svg = d3.select(d3Container.current)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
const color = d3.scaleOrdinal()
.domain(data.map(d => d.category))
.range(d3.schemeDark2);
setColors(Object.fromEntries(data.map(d => ([d.category, color(d.category)]))));
setDatas(data);
const pie = d3.pie()
.value(d => d.count)
.sort((a, b) => d3.ascending(a.category, b.category));
const arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
const data_ready = pie(data);
var u = svg.selectAll("path")
.data(data_ready);
u.enter()
.append('path')
.merge(u)
.transition()
.duration(1000)
.attr('d', arc)
.attr('fill', d => color(d.data.category))
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 1);
u.exit().remove();
// Adding labels to pie chart
u.enter()
.append("text")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) {
return d.data.count;
});
// Creating a color legend
const legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var horz = width / 2 + 2 * legendRectSize;
var vert = i * (legendRectSize + legendSpacing) - height / 2;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function (d) { return d; });
}
}, [d3Container.current])
.catch(err => console.log(err));
}, []);
return (
);
};
window.EventCategory = EventCategory;
useractivity.js:
import React, { StrictMode } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { useTable, useSortBy } from 'react-table';
const UserTable = ({ userName, data }) => {
const columns = useMemo(
() => [
{
Header: 'Category',
accessor: 'category',
},
{
Header: 'Action',
accessor: 'action',
},
{
Header: 'Time Stamp',
accessor: 'timeStamp',
},
],
[]
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({ columns, data }, useSortBy);
return (
1 ... 5 6 7 8 9 10 11 12 13
{userName}
);
};
const UserActivity = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch("/api/useractivity")
.then(response => response.json())
.then(data => setData(data))
.catch(err => console.log(err));
}, []);
const groupedData = useMemo(() => {
let groups = {};
data.forEach(item => {
if (!groups[item.userName]) {
groups[item.userName] = [];
}
groups[item.userName].push(item);
});
return groups;
}, [data]);
return (
<>
{Object.keys(groupedData).map(userName => {
return (
key={userName}
userName={userName}
data={groupedData[userName]}
/>
);
})}
>
);
};
window.UserActivity = UserActivity;
userjourney.js:
import React, { StrictMode } from 'react';
import { useState, useEffect } from 'react';
const UserJourney = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch("/api/userjourney")
.then(response => response.json())
.then(data => {
if (data) {
setData(data);
}
})
.catch(err => console.log(err));
}, []);
const ElementName = ({ userName }) =>
{userName}
const ElementEvent = ({ element }) => {
const title = `${element.eventAction}; ${element.eventUrl}; ${element.eventTimeStamp.slice(-5)}`;
const color = `rgb(${Math.floor(element.width * 155 / 100) + 100},0,0)`;
return
}
const EventContainer = ({ events }) => {
const max = new Date(events[events.length - 1].eventTimeStamp) - new Date(events[0].eventTimeStamp);
const getPrecent = (e, max) => Math.floor((e / max) * 100);
const getElement = (event, nextEvent) => {
if (!nextEvent) return { ...event, width: getPrecent(2000, max) };
return { ...event, width: getPrecent(new Date(nextEvent.eventTimeStamp) - new Date(event.eventTimeStamp), max) };
};
return (
{
events.map((element, index, original) =>
}
);
}
const JourneysListElement = (props) => {
const { data } = props
const { userName, userJourney } = data;
return (
);
}
const Journeys = (props) => {
const entries = [...data.entries()]
return
{
entries.map(([key, data]) =>
}
}
return (
};
window.UserJourney = UserJourney;
deviceusage.js:
import React, { StrictMode } from 'react';
import { useState, useEffect, useRef } from 'react';
import * as d3 from "d3";
const Legend = ({ data, color }) => {
return (
{
data.map((item, index) => (
{item.category}
))
}
);
};
const EventCategory = () => {
const d3Container = useRef(null);
const width = 450;
const height = 450;
const margin = 40;
const radius = Math.min(width, height) / 2 - margin;
const legendRectSize = 18;
const legendSpacing = 4;
const [datas, setDatas] = useState([]);
const [colors, setColors] = useState({});
useEffect(() => {
fetch("/api/eventcategory")
.then(response => response.json())
.then(data => {
console.log(data)
if (data && d3Container.current) {
const svg = d3.select(d3Container.current)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
const color = d3.scaleOrdinal()
.domain(data.map(d => d.category))
.range(d3.schemeDark2);
setColors(Object.fromEntries(data.map(d => ([d.category, color(d.category)]))));
setDatas(data);
const pie = d3.pie()
.value(d => d.count)
.sort((a, b) => d3.ascending(a.category, b.category));
const arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
const data_ready = pie(data);
var u = svg.selectAll("path")
.data(data_ready);
u.enter()
.append('path')
.merge(u)
.transition()
.duration(1000)
.attr('d', arc)
.attr('fill', d => color(d.data.category))
.attr("stroke", "white")
.style("stroke-width", "2px")
.style("opacity", 1);
u.exit().remove();
// Adding labels to pie chart
u.enter()
.append("text")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) {
return d.data.count;
});
// Creating a color legend
const legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var horz = width / 2 + 2 * legendRectSize;
var vert = i * (legendRectSize + legendSpacing) - height / 2;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function (d) { return d; });
}
}, [d3Container.current])
.catch(err => console.log(err));
}, []);
return (
);
};
window.EventCategory = EventCategory;
Приложение 4
1. Какие системы аналитики пользовательской активности в настоящее время используются в вашей организации? (Пожалуйста, выберите все подходящие варианты)
- Облачные сторонние решения
- Собственные решения, размещенные на месте
- Нет
2. Насколько важны для вашей организации конфиденциальность и безопасность данных при аналитике пользовательской активности?
• очень важно
• важно
• затрудняюсь ответить
• почти неважно
• неважно
3. Беспокоитесь ли вы о возможных утечках данных или несанкционированном доступе к данным аналитики пользовательской активности?
• очень беспокоюсь
• беспокоюсь
• затрудняюсь ответить
• почти не беспокоюсь
• не беспокоюсь
4. Насколько вы доверяете сторонним поставщикам в обеспечении безопасности данных аналитики пользовательской активности?
• очень доверяю
• доверяю
• затрудняюсь ответить
• почти не доверяю
• не доверяю
5. Насколько важно для вашей организации иметь полный контроль над хранением и обработкой данных аналитики пользовательской активности?
• очень важно
• важно
• затрудняюсь ответить
• почти неважно
• неважно
6. Хотели бы вы использование частной самоуправляемой системы аналитики пользовательской активности, которая позволяет полностью владеть и контролировать ваши данные?
• очень хочу
• хочу
• затрудняюсь ответить
• почти не хочу
• не хочу
7. Каковы основные причины рассмотрения варианта частной самоуправляемой системы аналитики пользовательской активности для вашей организации? (Пожалуйста, выберите все подходящие варианты)
• Повышенная конфиденциальность и безопасность данных
• Соблюдение правил защиты данных
• Снижение экспозиции данных перед сторонними поставщиками
• Полный контроль и владение данными
• Соответствие внутренним требованиям безопасности
• Другое (пожалуйста, укажите)
8. Насколько вы обеспокоены возможными утечками данных или несанкционированным доступом к данным аналитики пользовательской активности при использовании стороннего решения?
• очень обеспокоен
• обеспокоен
• затрудняюсь ответить
• почти не обеспокоен
• не обеспокоен
9. Насколько вам комфортно хранить данные аналитики пользовательской активности на серверах, контролируемых вашей организацией?
• очень комфортно
• комфортно
• затрудняюсь ответить
• почти не комфортно
• не комфортно
10. Готовы ли вы вложить ресурсы и создать инфраструктуру, необходимую для установки и поддержки частной самоуправляемой системы аналитики пользовательской активности?
• да
• затрудняюсь ответить
• нет
11. Предвидите ли вы проблемы при внедрении частной самоуправляемой системы аналитики пользовательской активности? (Пожалуйста, выберите все подходящие варианты)
• Технические знания, необходимые для установки и поддержки
• Более высокие начальные затраты на установку
• Постоянные затраты на поддержку и инфраструктуру
• Интеграция с существующими системами
• Ограниченная поддержка и документация
• Другое (пожалуйста, укажите)
12. Как вы оцениваете преимущества частной самоуправляемой системы аналитики пользовательской активности по сравнению с облачными сторонними решениями?
• очень комфортно
• комфортно
• затрудняюсь ответить
• почти не комфортно
• не комфортно
13. Будет ли ваша организация отдавать предпочтение частной самоуправляемой системе даже при необходимости больших ресурсов и усилий по сравнению с облачной альтернативой?
• да
• затрудняюсь ответить
• нет
14. Считаете ли вы, что частная самоуправляемая система аналитики пользовательской активности может обеспечить лучшую конфиденциальность данных и соответствие правилам защиты данных?
• да
• затрудняюсь ответить
• нет
15. Насколько важно для вашей организации иметь гибкость в настройке и адаптации системы аналитики пользовательской активности согласно специфическим потребностям и требованиям?
• очень важно
• важно
• затрудняюсь ответить
• почти неважно
• неважно
1 ... 5 6 7 8 9 10 11 12 13