Skip to content

Commit 5fd2b74

Browse files
lukaskabcledsoft
authored andcommitted
[Enhancement #519] Recent term history in vocabulary activity tab
Displaying recent term changes in vocabulary detail in activity tab.
1 parent b27f826 commit 5fd2b74

File tree

8 files changed

+202
-17
lines changed

8 files changed

+202
-17
lines changed

src/action/AsyncVocabularyActions.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { GetStoreState, ThunkDispatch } from "../util/Types";
1+
import { GetStoreState, PageRequest, ThunkDispatch } from "../util/Types";
22
import * as SyncActions from "./SyncActions";
33
import {
44
asyncActionFailure,
@@ -27,6 +27,10 @@ import SnapshotData, { CONTEXT as SNAPSHOT_CONTEXT } from "../model/Snapshot";
2727
import NotificationType from "../model/NotificationType";
2828
import ExportConfig from "../model/local/ExportConfig";
2929
import RDFStatement, { RDFSTATEMENT_CONTEXT } from "../model/RDFStatement";
30+
import ChangeRecord, {
31+
CONTEXT as CHANGE_RECORD_CONTEXT,
32+
} from "../model/changetracking/ChangeRecord";
33+
import AssetFactory from "../util/AssetFactory";
3034

3135
export function loadTermCount(vocabularyIri: IRI) {
3236
const action = { type: ActionType.LOAD_TERM_COUNT, vocabularyIri };
@@ -131,6 +135,39 @@ export function loadVocabularyContentChanges(vocabularyIri: IRI) {
131135
};
132136
}
133137

138+
export function loadVocabularyContentDetailedChanges(
139+
vocabularyIri: IRI,
140+
pageReq: PageRequest
141+
) {
142+
const action = {
143+
type: ActionType.LOAD_TERM_HISTORY,
144+
};
145+
146+
return (dispatch: ThunkDispatch) => {
147+
dispatch(asyncActionRequest(action, true));
148+
return Ajax.get(
149+
`${Constants.API_PREFIX}/vocabularies/${vocabularyIri.fragment}/history-of-content/detail`,
150+
param("namespace", vocabularyIri.namespace)
151+
.param("page", pageReq.page?.toString())
152+
.param("size", pageReq.size?.toString())
153+
)
154+
.then((data) =>
155+
JsonLdUtils.compactAndResolveReferencesAsArray<ChangeRecord>(
156+
data,
157+
CHANGE_RECORD_CONTEXT
158+
)
159+
)
160+
.then((data: ChangeRecord[]) => {
161+
dispatch(asyncActionSuccess(action));
162+
return data.map((r) => AssetFactory.createChangeRecord(r));
163+
})
164+
.catch((error: ErrorData) => {
165+
dispatch(asyncActionFailure(action, error));
166+
return [];
167+
});
168+
};
169+
}
170+
134171
export function loadRelatedVocabularies(vocabularyIri: IRI) {
135172
const action = { type: ActionType.LOAD_RELATED_VOCABULARIES, vocabularyIri };
136173
return (dispatch: ThunkDispatch) => {

src/component/changetracking/PersistRow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import PersistRecord from "../../model/changetracking/PersistRecord";
44
import { Badge } from "reactstrap";
55
import { useI18n } from "../hook/useI18n";
66

7-
interface PersistRowProps {
7+
export interface PersistRowProps {
88
record: PersistRecord;
99
}
1010

src/component/changetracking/UpdateRow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import OutgoingLink from "../misc/OutgoingLink";
99
import { Badge, Label } from "reactstrap";
1010
import { useI18n } from "../hook/useI18n";
1111

12-
interface UpdateRowProps {
12+
export interface UpdateRowProps {
1313
record: UpdateRecord;
1414
}
1515

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as React from "react";
2+
import { FormattedDate, FormattedTime } from "react-intl";
3+
import { Badge } from "reactstrap";
4+
import { useI18n } from "../hook/useI18n";
5+
import { PersistRowProps } from "./PersistRow";
6+
import TermIriLink from "../term/TermIriLink";
7+
8+
export const VocabularyContentPersistRow: React.FC<PersistRowProps> = (
9+
props
10+
) => {
11+
const { i18n } = useI18n();
12+
const record = props.record;
13+
const created = new Date(Date.parse(record.timestamp));
14+
return (
15+
<tr>
16+
<td>
17+
<div>
18+
<FormattedDate value={created} /> <FormattedTime value={created} />
19+
</div>
20+
<div className="italics last-edited-message ml-2">
21+
{record.author.fullName}
22+
</div>
23+
</td>
24+
<td>
25+
<TermIriLink iri={record.changedEntity.iri} />
26+
</td>
27+
<td>
28+
<Badge color="dark">{i18n(record.typeLabel)}</Badge>
29+
</td>
30+
<td />
31+
</tr>
32+
);
33+
};
34+
35+
export default VocabularyContentPersistRow;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as React from "react";
2+
import { FormattedDate, FormattedTime } from "react-intl";
3+
import AssetLabel from "../misc/AssetLabel";
4+
import { Badge } from "reactstrap";
5+
import { useI18n } from "../hook/useI18n";
6+
import { UpdateRowProps } from "./UpdateRow";
7+
import TermIriLink from "../term/TermIriLink";
8+
9+
export const VocabularyContentUpdateRow: React.FC<UpdateRowProps> = (props) => {
10+
const { i18n } = useI18n();
11+
const record = props.record;
12+
const created = new Date(Date.parse(record.timestamp));
13+
return (
14+
<tr>
15+
<td>
16+
<div>
17+
<FormattedDate value={created} /> <FormattedTime value={created} />
18+
</div>
19+
<div className="italics last-edited-message ml-2">
20+
{record.author.fullName}
21+
</div>
22+
</td>
23+
<td>
24+
<TermIriLink iri={record.changedEntity.iri} />
25+
</td>
26+
<td>
27+
<Badge color="secondary">{i18n(record.typeLabel)}</Badge>
28+
</td>
29+
<td>
30+
<AssetLabel iri={record.changedAttribute.iri} />
31+
</td>
32+
</tr>
33+
);
34+
};
35+
36+
export default VocabularyContentUpdateRow;

src/component/vocabulary/TermChangeFrequency.tsx

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,64 @@ import PromiseTrackingMask from "../misc/PromiseTrackingMask";
99
import { trackPromise } from "react-promise-tracker";
1010
import { useI18n } from "../hook/useI18n";
1111
import AggregatedChangeInfo from "../../model/changetracking/AggregatedChangeInfo";
12-
import { loadVocabularyContentChanges } from "../../action/AsyncVocabularyActions";
12+
import {
13+
loadVocabularyContentChanges,
14+
loadVocabularyContentDetailedChanges,
15+
} from "../../action/AsyncVocabularyActions";
16+
import ChangeRecord from "../../model/changetracking/ChangeRecord";
1317

1418
interface TermChangeFrequencyProps {
1519
vocabulary: Vocabulary;
1620
}
1721

1822
const TermChangeFrequency: React.FC<TermChangeFrequencyProps> = (props) => {
19-
const [records, setRecords] =
23+
const [aggregatedRecords, setAggregatedRecords] =
2024
React.useState<null | AggregatedChangeInfo[]>(null);
25+
const [changeRecords, setChangeRecords] =
26+
React.useState<null | ChangeRecord[]>(null);
2127
const { vocabulary } = props;
2228
const { i18n } = useI18n();
2329
const dispatch: ThunkDispatch = useDispatch();
30+
const [page, setPage] = React.useState(0);
2431
React.useEffect(() => {
2532
if (vocabulary.iri !== Constants.EMPTY_ASSET_IRI) {
2633
trackPromise(
2734
dispatch(
2835
loadVocabularyContentChanges(VocabularyUtils.create(vocabulary.iri))
29-
),
36+
).then((recs) => setAggregatedRecords(recs)),
3037
"term-change-frequency"
31-
).then((recs) => setRecords(recs));
38+
);
3239
}
3340
}, [vocabulary.iri, dispatch]);
3441

42+
React.useEffect(() => {
43+
if (vocabulary.iri !== Constants.EMPTY_ASSET_IRI) {
44+
trackPromise(
45+
dispatch(
46+
loadVocabularyContentDetailedChanges(
47+
VocabularyUtils.create(vocabulary.iri),
48+
{ page: page, size: Constants.VOCABULARY_CONTENT_HISTORY_LIMIT }
49+
)
50+
).then((changeRecords) => setChangeRecords(changeRecords)),
51+
"term-change-frequency"
52+
);
53+
}
54+
}, [vocabulary.iri, dispatch, page]);
55+
3556
return (
3657
<>
3758
<PromiseTrackingMask
3859
area="term-change-frequency"
3960
text={i18n("vocabulary.termchanges.loading")}
4061
/>
41-
<TermChangeFrequencyUI records={records} />
62+
<TermChangeFrequencyUI
63+
aggregatedRecords={aggregatedRecords}
64+
changeRecords={changeRecords}
65+
page={page}
66+
setPage={setPage}
67+
pageSize={Constants.VOCABULARY_CONTENT_HISTORY_LIMIT}
68+
itemCount={changeRecords?.length ?? 0}
69+
/>
4270
</>
4371
);
4472
};

src/component/vocabulary/TermChangeFrequencyUI.tsx

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import * as React from "react";
22
import Chart from "react-apexcharts";
3-
import { Col, Row } from "reactstrap";
3+
import { Col, Row, Table } from "reactstrap";
44
import { useI18n } from "../hook/useI18n";
55
import AggregatedChangeInfo from "../../model/changetracking/AggregatedChangeInfo";
66
import VocabularyUtils from "../../util/VocabularyUtils";
7+
import ChangeRecord from "../../model/changetracking/ChangeRecord";
8+
import { UpdateRecord } from "../../model/changetracking/UpdateRecord";
9+
import VocabularyContentPersistRow from "../changetracking/VocabularyContentPersistRow";
10+
import VocabularyContentUpdateRow from "../changetracking/VocabularyContentUpdateRow";
11+
import If from "../misc/If";
12+
import SimplePagination from "../dashboard/widget/lastcommented/SimplePagination";
713

814
interface TermChangeFrequencyUIProps {
9-
records: AggregatedChangeInfo[] | null;
15+
aggregatedRecords: AggregatedChangeInfo[] | null;
16+
changeRecords: ChangeRecord[] | null;
17+
page: number;
18+
setPage: (page: number) => void;
19+
pageSize: number;
20+
itemCount: number;
1021
}
1122

1223
/**
@@ -48,14 +59,19 @@ const CZ_LOCALE = {
4859
};
4960

5061
const TermChangeFrequencyUI: React.FC<TermChangeFrequencyUIProps> = ({
51-
records,
62+
aggregatedRecords,
63+
changeRecords,
64+
page,
65+
setPage,
66+
pageSize,
67+
itemCount,
5268
}) => {
5369
const { i18n, locale } = useI18n();
54-
if (!records) {
70+
if (!aggregatedRecords || !changeRecords) {
5571
return <div className="additional-metadata-container">&nbsp;</div>;
5672
}
5773

58-
if (records.length === 0) {
74+
if (aggregatedRecords.length === 0) {
5975
return (
6076
<div
6177
id="history-empty-notice"
@@ -66,11 +82,11 @@ const TermChangeFrequencyUI: React.FC<TermChangeFrequencyUIProps> = ({
6682
);
6783
}
6884

69-
const dates = Array.from(new Set(records.map((r) => r.getDate())));
70-
const termCreations = records.filter(
85+
const dates = Array.from(new Set(aggregatedRecords.map((r) => r.getDate())));
86+
const termCreations = aggregatedRecords.filter(
7187
(r) => r.types.indexOf(VocabularyUtils.PERSIST_EVENT) !== -1
7288
);
73-
const termUpdates = records.filter(
89+
const termUpdates = aggregatedRecords.filter(
7490
(r) => r.types.indexOf(VocabularyUtils.UPDATE_EVENT) !== -1
7591
);
7692

@@ -131,9 +147,40 @@ const TermChangeFrequencyUI: React.FC<TermChangeFrequencyUIProps> = ({
131147
];
132148
return (
133149
<Row>
134-
<Col xl={8} lg={12}>
150+
<Col xl={changeRecords.length === 0 ? 5 : 6} lg={12}>
135151
<Chart options={options} series={series} width="100%" />
136152
</Col>
153+
<Col xl={6} lg={12} className={"border-left"}>
154+
<div className="additional-metadata-container">
155+
<Table striped={true} responsive={true}>
156+
<thead>
157+
<tr>
158+
<th className="col-3">{i18n("history.whenwho")}</th>
159+
<th className="col-3">{i18n("type.term")}</th>
160+
<th className="col-1">{i18n("history.type")}</th>
161+
<th className="col-2">{i18n("history.changedAttribute")}</th>
162+
</tr>
163+
</thead>
164+
<tbody>
165+
{changeRecords.map((r) =>
166+
r instanceof UpdateRecord ? (
167+
<VocabularyContentUpdateRow key={r.iri} record={r} />
168+
) : (
169+
<VocabularyContentPersistRow key={r.iri} record={r} />
170+
)
171+
)}
172+
</tbody>
173+
</Table>
174+
</div>
175+
<If expression={changeRecords.length > 0}>
176+
<SimplePagination
177+
page={page}
178+
setPage={setPage}
179+
pageSize={pageSize}
180+
itemCount={itemCount}
181+
/>
182+
</If>
183+
</Col>
137184
</Row>
138185
);
139186
};

src/util/Constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ const constants = {
121121

122122
// Size of page fetched from server
123123
DEFAULT_PAGE_SIZE: 100,
124+
// size of the page for change records in vocabulary activity tab
125+
VOCABULARY_CONTENT_HISTORY_LIMIT: 15,
124126

125127
WEBSOCKET_ENDPOINT: {
126128
VOCABULARIES_VALIDATION: "/vocabularies/validation",

0 commit comments

Comments
 (0)