Made query page display some results

Fixed Activity group filter
This commit is contained in:
Aurora Lahtela 2022-11-12 10:39:20 +02:00
parent 70857857df
commit 38774867f7
12 changed files with 225 additions and 22 deletions

View File

@ -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);

View File

@ -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"),

View File

@ -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)
);
}

View File

@ -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>}/>

View 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

View File

@ -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}/>

View File

@ -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>

View 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);
}

View File

@ -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);
}

View File

@ -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,18 +39,20 @@ const QueryPage = () => {
return (
<>
<NightModeCss/>
<Sidebar items={sidebarItems} showBackButton={showBackButton}/>
<div className="d-flex flex-column" id="content-wrapper">
<Header page={displayedServerName} tab={currentTab} hideUpdater/>
<div id="content" style={{display: 'flex'}}>
<main className="container-fluid mt-4">
<Outlet context={{}}/>
</main>
<aside>
<ColorSelectorModal/>
</aside>
<QueryResultContextProvider>
<Sidebar items={sidebarItems} showBackButton={showBackButton}/>
<div className="d-flex flex-column" id="content-wrapper">
<Header page={displayedServerName} tab={currentTab} hideUpdater/>
<div id="content" style={{display: 'flex'}}>
<main className="container-fluid mt-4">
<Outlet context={{}}/>
</main>
<aside>
<ColorSelectorModal/>
</aside>
</div>
</div>
</div>
</QueryResultContextProvider>
</>
)
}

View File

@ -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>

View 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