mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-01-03 15:08:12 +01:00
Made query page display some results
Fixed Activity group filter
This commit is contained in:
parent
70857857df
commit
38774867f7
@ -142,7 +142,7 @@ public class QueryJSONResolver implements Resolver {
|
||||
List<Filter.ResultPath> resultPath = result.getInverseResultPath();
|
||||
Collections.reverse(resultPath);
|
||||
|
||||
return buildAndStoreResponse(inputQuery.getView(), result, resultPath);
|
||||
return buildAndStoreResponse(inputQuery, result, resultPath);
|
||||
}
|
||||
|
||||
private InputQueryDto parseInputQuery(Request request) {
|
||||
@ -180,16 +180,17 @@ public class QueryJSONResolver implements Resolver {
|
||||
}
|
||||
}
|
||||
|
||||
private Response buildAndStoreResponse(ViewDto view, Filter.Result result, List<Filter.ResultPath> resultPath) {
|
||||
private Response buildAndStoreResponse(InputQueryDto input, Filter.Result result, List<Filter.ResultPath> resultPath) {
|
||||
try {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
Map<String, Object> json = Maps.builder(String.class, Object.class)
|
||||
.put("path", resultPath)
|
||||
.put("view", view)
|
||||
.put("view", input.getView())
|
||||
.put("filters", input.getFilters())
|
||||
.put("timestamp", timestamp)
|
||||
.build();
|
||||
if (!result.isEmpty()) {
|
||||
json.put("data", getDataFor(result.getResultUserIds(), view));
|
||||
json.put("data", getDataFor(result.getResultUserIds(), input.getView()));
|
||||
}
|
||||
|
||||
JSONStorage.StoredJSON stored = jsonStorage.storeJson("query", json, timestamp);
|
||||
|
@ -60,6 +60,7 @@ public enum JSLang implements Lang {
|
||||
QUERY_HAVE_PLUGIN_BOOLEAN_VALUE("html.query.filter.hasPluginBooleanValue.text", "have Plugin boolean value"),
|
||||
QUERY_HAS_PLAYED_ON_SERVERS("html.query.filter.hasPlayedOnServers.name", "Has played on one of servers"),
|
||||
QUERY_HAVE_PLAYED_ON_SERVERS("html.query.filter.hasPlayedOnServers.text", "have played on at least one of"),
|
||||
FILTER_SKIPPED("html.query.filter.skipped", "Skipped"),
|
||||
|
||||
FILTER_GROUP("html.query.filter.pluginGroup.name", "Group: "),
|
||||
FILTER_ALL_PLAYERS("html.query.filter.generic.allPlayers", "All players"),
|
||||
|
@ -449,7 +449,7 @@ public class NetworkActivityIndexQueries {
|
||||
Map<Integer, ActivityIndex> indexes = new HashMap<>();
|
||||
while (set.next()) {
|
||||
indexes.put(
|
||||
set.getInt(UsersTable.ID),
|
||||
set.getInt("user_id"),
|
||||
new ActivityIndex(set.getDouble("activity_index"), date)
|
||||
);
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ const AllPlayers = React.lazy(() => import("./views/players/AllPlayers"));
|
||||
|
||||
const QueryPage = React.lazy(() => import("./views/layout/QueryPage"));
|
||||
const NewQueryView = React.lazy(() => import("./views/query/NewQueryView"));
|
||||
const QueryResultView = React.lazy(() => import("./views/query/QueryResultView"));
|
||||
|
||||
const ErrorsPage = React.lazy(() => import("./views/layout/ErrorsPage"));
|
||||
const SwaggerView = React.lazy(() => import("./views/SwaggerView"));
|
||||
@ -147,6 +148,7 @@ function App() {
|
||||
<Route path="/query" element={<Lazy><QueryPage/></Lazy>}>
|
||||
<Route path="" element={<NewRedirect/>}/>
|
||||
<Route path="new" element={<Lazy><NewQueryView/></Lazy>}/>
|
||||
<Route path="result" element={<Lazy><QueryResultView/></Lazy>}/>
|
||||
</Route>
|
||||
<Route path="/errors" element={<Lazy><ErrorsPage/></Lazy>}/>
|
||||
<Route path="/docs" element={<Lazy><SwaggerView/></Lazy>}/>
|
||||
|
59
Plan/react/dashboard/src/components/alert/QueryPath.js
Normal file
59
Plan/react/dashboard/src/components/alert/QueryPath.js
Normal file
@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import {useQueryResultContext} from "../../hooks/queryResultContext";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faFilter} from "@fortawesome/free-solid-svg-icons";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
const QueryPath = () => {
|
||||
const {t} = useTranslation();
|
||||
const {result} = useQueryResultContext();
|
||||
const hasResults = result && result.data;
|
||||
const path = result?.path;
|
||||
if (!path || !path.length) return <></>;
|
||||
|
||||
|
||||
const getReadableFilterName = kind => {
|
||||
if (kind.endsWith(" (skip)")) {
|
||||
return getReadableFilterName(kind.substring(0, kind.length - 5)) + " (" + t('html.query.filter.skipped') + ")";
|
||||
}
|
||||
|
||||
if (kind.startsWith("pluginGroups-")) {
|
||||
return "Group: " + kind.substring(13);
|
||||
}
|
||||
switch (kind) {
|
||||
case "allPlayers":
|
||||
return t('html.query.filter.generic.allPlayers')
|
||||
case "activityIndexNow":
|
||||
return t('html.query.filter.title.activityGroup');
|
||||
case "banned":
|
||||
return t('html.query.filter.banStatus.name');
|
||||
case "operators":
|
||||
return t('html.query.filter.operatorStatus.name');
|
||||
case "joinAddresses":
|
||||
return t('html.label.joinAddresses');
|
||||
case "geolocations":
|
||||
return t('html.label.geolocations');
|
||||
case "playedBetween":
|
||||
return t('html.query.filter.playedBetween.text');
|
||||
case "registeredBetween":
|
||||
return t('html.query.filter.registeredBetween.text');
|
||||
case "pluginsBooleanGroups":
|
||||
return t('html.query.filter.hasPluginBooleanValue.name');
|
||||
case "playedOnServer":
|
||||
return t('html.query.filter.hasPlayedOnServers.name');
|
||||
default:
|
||||
return kind.kind;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<aside id={"result-path"} className={"alert shadow " + (hasResults ? "alert-success" : "alert-warning")}>
|
||||
{path.map((step, i) => <p key={i} style={{marginBottom: 0, marginLeft: i * 0.7 + "rem"}}>
|
||||
<FontAwesomeIcon
|
||||
icon={faFilter}/> '{getReadableFilterName(step.kind)}' matched {step.size} players
|
||||
</p>)}
|
||||
</aside>
|
||||
)
|
||||
};
|
||||
|
||||
export default QueryPath
|
@ -7,7 +7,7 @@ import DataTablesTable from "../../table/DataTablesTable";
|
||||
import {CardLoader} from "../../navigation/Loader";
|
||||
import {baseAddress} from "../../../service/backendConfiguration";
|
||||
|
||||
const PlayerListCard = ({data}) => {
|
||||
const PlayerListCard = ({data, title}) => {
|
||||
const {t} = useTranslation();
|
||||
const [options, setOptions] = useState(undefined);
|
||||
|
||||
@ -34,7 +34,7 @@ const PlayerListCard = ({data}) => {
|
||||
<Card>
|
||||
<Card.Header>
|
||||
<h6 className="col-black">
|
||||
<Fa icon={faUsers} className="col-black"/> {t('html.label.playerList')}
|
||||
<Fa icon={faUsers} className="col-black"/> {title ? title : t('html.label.playerList')}
|
||||
</h6>
|
||||
</Card.Header>
|
||||
<DataTablesTable id={"players-table"} options={options}/>
|
||||
|
@ -2,7 +2,7 @@ import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {Card, Col, Row} from "react-bootstrap-v5";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useDataRequest} from "../../../hooks/dataFetchHook";
|
||||
import {fetchFilters} from "../../../service/queryService";
|
||||
import {fetchFilters, postQuery} from "../../../service/queryService";
|
||||
import {ErrorViewCard} from "../../../views/ErrorView";
|
||||
import {ChartLoader} from "../../navigation/Loader";
|
||||
import DateInputField from "../../input/DateInputField";
|
||||
@ -15,6 +15,8 @@ import MultiSelect from "../../input/MultiSelect";
|
||||
import CollapseWithButton from "../../layout/CollapseWithButton";
|
||||
import FilterDropdown from "./FilterDropdown";
|
||||
import FilterList from "./FilterList";
|
||||
import {useQueryResultContext} from "../../../hooks/queryResultContext";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
const parseTime = (dateString, timeString) => {
|
||||
const d = dateString.match(
|
||||
@ -34,6 +36,8 @@ const parseTime = (dateString, timeString) => {
|
||||
|
||||
const QueryOptionsCard = () => {
|
||||
const {t} = useTranslation();
|
||||
const navigate = useNavigate()
|
||||
const {setResult} = useQueryResultContext();
|
||||
|
||||
// View state
|
||||
const [fromDate, setFromDate] = useState(undefined);
|
||||
@ -49,7 +53,6 @@ const QueryOptionsCard = () => {
|
||||
const [graphData, setGraphData] = useState(undefined);
|
||||
useEffect(() => {
|
||||
if (options) {
|
||||
console.log("Graph data loaded")
|
||||
setGraphData({playersOnline: options.viewPoints, color: '#9E9E9E'})
|
||||
}
|
||||
}, [options, setGraphData]);
|
||||
@ -109,6 +112,27 @@ const QueryOptionsCard = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const performQuery = async () => {
|
||||
const inputDto = {
|
||||
view: {
|
||||
afterDate: fromDate ? fromDate : options.view.afterDate,
|
||||
afterTime: fromTime ? fromTime : options.view.afterTime,
|
||||
beforeDate: toDate ? toDate : options.view.beforeDate,
|
||||
beforeTime: toTime ? toTime : options.view.beforeTime,
|
||||
servers: selectedServers.map(index => options.view.servers[index])
|
||||
},
|
||||
filters
|
||||
}
|
||||
|
||||
// TODO handle error
|
||||
const {data} = await postQuery(inputDto);
|
||||
setResult(data);
|
||||
window.scrollTo(0, 0);
|
||||
if (data?.data) {
|
||||
navigate('../result?timestamp=' + data.timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
if (loadingError) return <ErrorViewCard error={loadingError}/>
|
||||
if (!options) return (<Card>
|
||||
<Card.Body>
|
||||
@ -198,7 +222,10 @@ const QueryOptionsCard = () => {
|
||||
</Col>
|
||||
</Row>
|
||||
</Card.Body>
|
||||
<button id={"query-button"} className={"btn bg-plan m-2"} disabled={Boolean(invalidFields.length)}>
|
||||
<button id={"query-button"}
|
||||
className={"btn bg-plan m-2"}
|
||||
disabled={Boolean(invalidFields.length)}
|
||||
onClick={performQuery}>
|
||||
<FontAwesomeIcon icon={faSearch}/> {t('html.query.performQuery')}
|
||||
</button>
|
||||
</Card>
|
||||
|
17
Plan/react/dashboard/src/hooks/queryResultContext.js
Normal file
17
Plan/react/dashboard/src/hooks/queryResultContext.js
Normal file
@ -0,0 +1,17 @@
|
||||
import {createContext, useContext, useState} from "react";
|
||||
|
||||
const QueryResultContext = createContext({});
|
||||
|
||||
export const QueryResultContextProvider = ({children}) => {
|
||||
const [result, setResult] = useState({});
|
||||
|
||||
const sharedState = {result, setResult}
|
||||
return (<QueryResultContext.Provider value={sharedState}>
|
||||
{children}
|
||||
</QueryResultContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useQueryResultContext = () => {
|
||||
return useContext(QueryResultContext);
|
||||
}
|
@ -1,6 +1,16 @@
|
||||
import {doGetRequest} from "./backendConfiguration";
|
||||
import {doGetRequest, doSomePostRequest, standard200option} from "./backendConfiguration";
|
||||
|
||||
export const fetchFilters = async () => {
|
||||
const url = `/v1/filters`;
|
||||
return doGetRequest(url);
|
||||
}
|
||||
|
||||
export const postQuery = async (inputDto) => {
|
||||
const url = `/v1/query`;
|
||||
return doSomePostRequest(url, [standard200option], inputDto);
|
||||
}
|
||||
|
||||
export const fetchExistingResults = async (timestamp) => {
|
||||
const url = `/v1/query?timestamp=${timestamp}`;
|
||||
return doGetRequest(url);
|
||||
}
|
@ -9,6 +9,7 @@ import Header from "../../components/navigation/Header";
|
||||
import ColorSelectorModal from "../../components/modal/ColorSelectorModal";
|
||||
import {useMetadata} from "../../hooks/metadataHook";
|
||||
import ErrorPage from "./ErrorPage";
|
||||
import {QueryResultContextProvider} from "../../hooks/queryResultContext";
|
||||
|
||||
const QueryPage = () => {
|
||||
const {t, i18n} = useTranslation();
|
||||
@ -38,6 +39,7 @@ const QueryPage = () => {
|
||||
return (
|
||||
<>
|
||||
<NightModeCss/>
|
||||
<QueryResultContextProvider>
|
||||
<Sidebar items={sidebarItems} showBackButton={showBackButton}/>
|
||||
<div className="d-flex flex-column" id="content-wrapper">
|
||||
<Header page={displayedServerName} tab={currentTab} hideUpdater/>
|
||||
@ -50,6 +52,7 @@ const QueryPage = () => {
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</QueryResultContextProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import LoadIn from "../../components/animation/LoadIn";
|
||||
import {Col, Row} from "react-bootstrap-v5";
|
||||
import QueryOptionsCard from "../../components/cards/query/QueryOptionsCard";
|
||||
import QueryPath from "../../components/alert/QueryPath";
|
||||
|
||||
const NewQueryView = () => {
|
||||
return (
|
||||
@ -9,6 +10,7 @@ const NewQueryView = () => {
|
||||
<section className={"query-options-view"}>
|
||||
<Row>
|
||||
<Col md={12}>
|
||||
<QueryPath/>
|
||||
<QueryOptionsCard/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
81
Plan/react/dashboard/src/views/query/QueryResultView.js
Normal file
81
Plan/react/dashboard/src/views/query/QueryResultView.js
Normal file
@ -0,0 +1,81 @@
|
||||
import React, {useCallback, useEffect} from 'react';
|
||||
import LoadIn from "../../components/animation/LoadIn";
|
||||
import {Col, Row} from "react-bootstrap-v5";
|
||||
import QueryPath from "../../components/alert/QueryPath";
|
||||
import {useQueryResultContext} from "../../hooks/queryResultContext";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import PlayerListCard from "../../components/cards/common/PlayerListCard";
|
||||
import {fetchExistingResults} from "../../service/queryService";
|
||||
|
||||
const QueryResultView = () => {
|
||||
const navigate = useNavigate();
|
||||
const {result, setResult} = useQueryResultContext();
|
||||
|
||||
const getResult = useCallback(async () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const timestamp = urlParams.get('timestamp');
|
||||
if (!timestamp) return {};
|
||||
|
||||
const {data: result} = await fetchExistingResults(timestamp);
|
||||
if (result) {
|
||||
return result;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!result.data) {
|
||||
getResult().then(data => {
|
||||
if (data.data) {
|
||||
setResult(data);
|
||||
} else {
|
||||
navigate('../new');
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [result, navigate, getResult, setResult])
|
||||
|
||||
if (!result.data) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const getViewTitle = () => {
|
||||
return 'View: ' + result.view.afterDate + " - " + result.view.beforeDate + ', ' +
|
||||
(result.view.servers.len ? 'using data of servers: ' + result.view.servers.map(server => server.name).join(', ') : "using data of all servers")
|
||||
}
|
||||
|
||||
return (
|
||||
<LoadIn>
|
||||
<section className={"query-results-view"}>
|
||||
<Row>
|
||||
<Col md={12}>
|
||||
<QueryPath/>
|
||||
<PlayerListCard
|
||||
data={result.data.players}
|
||||
title={getViewTitle()}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col lg={8}>
|
||||
{/*<PlayerbaseDevelopmentCard/>*/}
|
||||
</Col>
|
||||
<Col lg={4}>
|
||||
{/*<CurrentPlayerbaseCard/>*/}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col lg={3}>
|
||||
{/*<SessionsWithinViewCard/>*/}
|
||||
</Col>
|
||||
<Col lg={9}>
|
||||
{/*<GeolocationsCard/>*/}
|
||||
</Col>
|
||||
</Row>
|
||||
</section>
|
||||
</LoadIn>
|
||||
)
|
||||
};
|
||||
|
||||
export default QueryResultView
|
Loading…
Reference in New Issue
Block a user