Skip to content

Commit

Permalink
[Enhancement kbss-cvut#520] Vocabulary activity term changes filter b…
Browse files Browse the repository at this point in the history
…ackend requests

Removed time range input and implemented requesting data from BE using filter parameters.
  • Loading branch information
lukaskabc committed Nov 4, 2024
1 parent f3ebbf4 commit fa1b65e
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 81 deletions.
25 changes: 21 additions & 4 deletions src/action/AsyncVocabularyActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
publishMessage,
publishNotification,
} from "./SyncActions";
import { IRI } from "../util/VocabularyUtils";
import VocabularyUtils, { IRI } from "../util/VocabularyUtils";
import ActionType from "./ActionType";
import Ajax, { param } from "../util/Ajax";
import Constants from "../util/Constants";
Expand All @@ -31,6 +31,7 @@ import ChangeRecord, {
CONTEXT as CHANGE_RECORD_CONTEXT,
} from "../model/changetracking/ChangeRecord";
import AssetFactory from "../util/AssetFactory";
import { VocabularyContentChangeFilterData } from "../model/filter/VocabularyContentChangeFilterData";

export function loadTermCount(vocabularyIri: IRI) {
const action = { type: ActionType.LOAD_TERM_COUNT, vocabularyIri };
Expand Down Expand Up @@ -137,6 +138,7 @@ export function loadVocabularyContentChanges(vocabularyIri: IRI) {

export function loadVocabularyContentDetailedChanges(
vocabularyIri: IRI,
filterData: VocabularyContentChangeFilterData,
pageReq: PageRequest
) {
const action = {
Expand All @@ -145,11 +147,26 @@ export function loadVocabularyContentDetailedChanges(

return (dispatch: ThunkDispatch) => {
dispatch(asyncActionRequest(action, true));
let params = param("namespace", vocabularyIri.namespace)
.param("page", pageReq.page?.toString())
.param("size", pageReq.size?.toString());
for (const [key, value] of Object.entries(filterData)) {
params = params.param(key, value);
}
switch (params.getParams()?.["type"]) {
case "history.type.persist":
params = params.param("type", VocabularyUtils.PERSIST_EVENT);
break;
case "history.type.update":
params = params.param("type", VocabularyUtils.UPDATE_EVENT);
break;
case "history.type.delete":
params = params.param("type", VocabularyUtils.DELETE_EVENT);
break;
}
return Ajax.get(
`${Constants.API_PREFIX}/vocabularies/${vocabularyIri.fragment}/history-of-content/detail`,
param("namespace", vocabularyIri.namespace)
.param("page", pageReq.page?.toString())
.param("size", pageReq.size?.toString())
params
)
.then((data) =>
JsonLdUtils.compactAndResolveReferencesAsArray<ChangeRecord>(
Expand Down
37 changes: 37 additions & 0 deletions src/component/changetracking/VocabularyContentDeleteRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from "react";
import { FormattedDate, FormattedTime } from "react-intl";
import { Badge } from "reactstrap";
import { useI18n } from "../hook/useI18n";
import TermIriLink from "../term/TermIriLink";
import DeleteRecord from "../../model/changetracking/DeleteRecord";

export interface DeleteRowProps {
record: DeleteRecord;
}

export const VocabularyContentDeleteRow: React.FC<DeleteRowProps> = (props) => {
const { i18n } = useI18n();
const record = props.record;
const created = new Date(Date.parse(record.timestamp));
return (
<tr>
<td>
<div>
<FormattedDate value={created} /> <FormattedTime value={created} />
</div>
<div className="italics last-edited-message ml-2">
{record.author.fullName}
</div>
</td>
<td>
<TermIriLink iri={record.changedEntity.iri} />
</td>
<td>
<Badge color="danger">{i18n(record.typeLabel)}</Badge>
</td>
<td></td>
</tr>
);
};

export default VocabularyContentDeleteRow;
14 changes: 12 additions & 2 deletions src/component/vocabulary/TermChangeFrequency.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from "react";
import { useState } from "react";
import Vocabulary from "../../model/Vocabulary";
import { ThunkDispatch } from "../../util/Types";
import { useDispatch } from "react-redux";
Expand All @@ -14,6 +15,7 @@ import {
loadVocabularyContentDetailedChanges,
} from "../../action/AsyncVocabularyActions";
import ChangeRecord from "../../model/changetracking/ChangeRecord";
import { VocabularyContentChangeFilterData } from "../../model/filter/VocabularyContentChangeFilterData";

interface TermChangeFrequencyProps {
vocabulary: Vocabulary;
Expand All @@ -30,6 +32,13 @@ const TermChangeFrequency: React.FC<TermChangeFrequencyProps> = (props) => {
const { i18n } = useI18n();
const dispatch: ThunkDispatch = useDispatch();
const [page, setPage] = React.useState(0);
const [filterData, setFilterData] =
useState<VocabularyContentChangeFilterData>({
term: "",
type: "",
attribute: "",
author: "",
});
React.useEffect(() => {
if (vocabulary.iri !== Constants.EMPTY_ASSET_IRI) {
trackPromise(
Expand All @@ -47,13 +56,14 @@ const TermChangeFrequency: React.FC<TermChangeFrequencyProps> = (props) => {
dispatch(
loadVocabularyContentDetailedChanges(
VocabularyUtils.create(vocabulary.iri),
filterData,
{ page: page, size: Constants.VOCABULARY_CONTENT_HISTORY_LIMIT }
)
).then((changeRecords) => setChangeRecords(changeRecords)),
"term-change-frequency"
);
}
}, [vocabulary.iri, dispatch, page]);
}, [vocabulary.iri, dispatch, page, filterData]);

return (
<>
Expand All @@ -67,7 +77,7 @@ const TermChangeFrequency: React.FC<TermChangeFrequencyProps> = (props) => {
page={page}
setPage={setPage}
pageSize={Constants.VOCABULARY_CONTENT_HISTORY_LIMIT}
itemCount={changeRecords?.length ?? 0}
applyFilter={setFilterData}
/>
</>
);
Expand Down
165 changes: 90 additions & 75 deletions src/component/vocabulary/TermChangeFrequencyUI.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from "react";
import { useCallback, useEffect, useState } from "react";
import Chart from "react-apexcharts";
import { Col, Row, Table } from "reactstrap";
import { useI18n } from "../hook/useI18n";
Expand All @@ -13,15 +14,20 @@ import SimplePagination from "../dashboard/widget/lastcommented/SimplePagination
import CustomInput from "../misc/CustomInput";
import Select from "../misc/Select";
import "./TermChangeFrequencyUI.scss";
import classNames from "classnames";
import PersistRecord from "../../model/changetracking/PersistRecord";
import DeleteRecord from "../../model/changetracking/DeleteRecord";
import VocabularyContentDeleteRow from "../changetracking/VocabularyContentDeleteRow";
import { debounce } from "lodash";
import Constants from "../../util/Constants";
import { VocabularyContentChangeFilterData } from "../../model/filter/VocabularyContentChangeFilterData";

interface TermChangeFrequencyUIProps {
aggregatedRecords: AggregatedChangeInfo[] | null;
changeRecords: ChangeRecord[] | null;
page: number;
setPage: (page: number) => void;
pageSize: number;
itemCount: number;
applyFilter: (filterData: VocabularyContentChangeFilterData) => void;
}

/**
Expand Down Expand Up @@ -68,10 +74,32 @@ const TermChangeFrequencyUI: React.FC<TermChangeFrequencyUIProps> = ({
page,
setPage,
pageSize,
itemCount,
applyFilter,
}) => {
const { i18n, locale } = useI18n();
const [showFilter, setShowFilter] = React.useState(false);

const [filterAuthor, setFilterAuthor] = useState("");
const [filterTerm, setFilterTerm] = useState("");
const [filterType, setFilterType] = useState("");
const [filterAttribute, setFilterAttribute] = useState("");
const applyFilterDebounced = useCallback(
debounce(
(filterData: VocabularyContentChangeFilterData) =>
applyFilter(filterData),
Constants.INPUT_DEBOUNCE_WAIT_TIME
),
[applyFilter]
);

useEffect(() => {
applyFilterDebounced({
author: filterTerm,
term: filterTerm,
type: filterType,
attribute: filterAttribute,
});
}, [filterAuthor, filterTerm, filterType, filterAttribute]);

if (!aggregatedRecords || !changeRecords) {
return <div className="additional-metadata-container">&nbsp;</div>;
}
Expand Down Expand Up @@ -152,90 +180,77 @@ const TermChangeFrequencyUI: React.FC<TermChangeFrequencyUIProps> = ({
];
return (
<Row>
<Col xl={changeRecords.length === 0 ? 5 : 6} lg={12}>
<Col xl={6} lg={12}>
<Chart options={options} series={series} width="100%" />
</Col>
<Col xl={6} lg={12} className={"border-left"}>
<div className="additional-metadata-container">
<Table striped={true} responsive={true}>
<thead>
<If expression={showFilter}>
<tr>
<td className={"col-3 border-0"} colSpan={2}>
<CustomInput
name="date-filter"
placeholder={i18n("history.filter.datetime")}
value={""}
onChange={(e) => {}}
/>
</td>
</tr>
</If>
<tr>
<th className="col-3">{i18n("history.whenwho")}</th>
<th className="col-3">{i18n("type.term")}</th>
<th className="col-1">{i18n("history.type")}</th>
<th className="col d-flex justify-content-between">
{i18n("history.changedAttribute")}
<span
className={classNames("cursor-pointer", {
"color-primary": showFilter,
})}
onClick={() => setShowFilter(!showFilter)}
title={i18n("main.nav.search")}
<th className="col">{i18n("history.changedAttribute")}</th>
</tr>
<tr>
<td className="col-3">
<CustomInput
name={i18n("asset.author")}
placeholder={i18n("asset.author")}
value={filterAuthor}
onChange={(e) => setFilterAuthor(e.target.value)}
/>
</td>
<td className="col-3">
<CustomInput
name={i18n("type.term")}
placeholder={i18n("type.term")}
value={filterTerm}
onChange={(e) => setFilterTerm(e.target.value)}
/>
</td>
<td className={"col-2"}>
<Select
placeholder={i18n("history.type")}
value={filterType}
onChange={(e) => setFilterType(e.target.value)}
>
<i className={"fas fa-search fa-lg"} />
</span>
</th>
<option value={""}></option>
{[
"history.type.persist",
"history.type.update",
"history.type.delete",
].map((type) => (
<option key={type} value={type}>
{i18n(type)}
</option>
))}
</Select>
</td>
<td className="col-2">
<CustomInput
name={i18n("history.changedAttribute")}
placeholder={i18n("history.changedAttribute")}
value={filterAttribute}
onChange={(e) => setFilterAttribute(e.target.value)}
/>
</td>
</tr>
<If expression={showFilter}>
<tr>
<td className="col-3">
<CustomInput
name={i18n("asset.author")}
placeholder={i18n("asset.author")}
/>
</td>
<td className="col-3">
<CustomInput
name={i18n("type.term")}
placeholder={i18n("type.term")}
/>
</td>
<td className={"col-2"}>
<Select
placeholder={i18n("history.type")}
value={i18n("history.type")}
>
<option value={""}></option>
{[
"history.type.persist",
"history.type.update",
"history.type.delete",
].map((type) => (
<option key={type} value={type}>
{i18n(type)}
</option>
))}
</Select>
</td>
<td className="col-2">
<CustomInput
name={i18n("history.changedAttribute")}
placeholder={i18n("history.changedAttribute")}
/>
</td>
</tr>
</If>
</thead>
<tbody>
{changeRecords.map((r) =>
r instanceof UpdateRecord ? (
<VocabularyContentUpdateRow key={r.iri} record={r} />
) : (
<VocabularyContentPersistRow key={r.iri} record={r} />
)
)}
{changeRecords.map((r) => {
if (r instanceof PersistRecord) {
return <VocabularyContentPersistRow key={r.iri} record={r} />;
}
if (r instanceof UpdateRecord) {
return <VocabularyContentUpdateRow key={r.iri} record={r} />;
}
if (r instanceof DeleteRecord) {
return <VocabularyContentDeleteRow key={r.iri} record={r} />;
}
return null;
})}
</tbody>
</Table>
</div>
Expand All @@ -244,7 +259,7 @@ const TermChangeFrequencyUI: React.FC<TermChangeFrequencyUIProps> = ({
page={page}
setPage={setPage}
pageSize={pageSize}
itemCount={itemCount}
itemCount={pageSize + 1}
/>
</If>
</Col>
Expand Down
22 changes: 22 additions & 0 deletions src/model/changetracking/DeleteRecord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import ChangeRecord, { ChangeRecordData } from "./ChangeRecord";
import MultilingualString from "../MultilingualString";

export interface DeleteRecordData extends ChangeRecordData {
label: MultilingualString;
}

/**
* Represents insertion of an entity into the repository.
*/
export default class DeleteRecord extends ChangeRecord {
public readonly label: MultilingualString;
public readonly vocabulary?: string;
public constructor(data: DeleteRecordData) {
super(data);
this.label = data.label;
}

get typeLabel(): string {
return "history.type.delete";
}
}
6 changes: 6 additions & 0 deletions src/model/filter/VocabularyContentChangeFilterData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface VocabularyContentChangeFilterData {
author: string;
term: string;
type: string;
attribute: string;
}
Loading

0 comments on commit fa1b65e

Please sign in to comment.