Fix history store handling, avoid overwriting loaded attributes

This commit is contained in:
guerler
2022-09-18 15:23:08 -04:00
parent 3e3a7f6582
commit cb0fc74b02
5 changed files with 34 additions and 143 deletions
+20 -4
View File
@@ -1,17 +1,30 @@
<template>
<CurrentUser v-slot="{ user }">
<UserHistories v-if="user" v-slot="{ handlers }" :user="user">
<div v-if="getHistory" class="d-flex flex-column h-100">
<div v-if="history" class="d-flex flex-column h-100">
<div class="flex-row flex-grow-0">
<b-button
v-if="user.id == history.user_id"
size="sm"
variant="outline-info"
title="Switch to this history"
@click="handlers.setCurrentHistory(history)">
Switch to this history
</b-button>
<b-button v-else size="sm" variant="outline-info" title="Import this history">
Import this history
</b-button>
</div>
<CollectionPanel
v-if="selectedCollections.length && selectedCollections[0].history_id == id"
:history="getHistory"
:history="history"
:selected-collections.sync="selectedCollections"
:show-controls="false"
@view-collection="onViewCollection" />
<HistoryPanel
v-else
v-on="handlers"
:history="getHistory"
:history="history"
:show-controls="false"
@view-collection="onViewCollection" />
</div>
@@ -49,9 +62,12 @@ export default {
selectedCollections: [],
};
},
created() {
this.$store.dispatch("history/loadHistoryById", this.id);
},
computed: {
...mapGetters({ getHistoryById: "history/getHistoryById" }),
getHistory() {
history() {
return this.getHistoryById(this.id);
},
},
@@ -32,14 +32,13 @@ const mutations = {
Vue.delete(state.histories, doomed.id);
},
setHistories(state, newHistories = []) {
const currentHistoryId = state.currentHistoryId;
const currentHistory = state.histories[currentHistoryId];
const newMap = newHistories.reduce((acc, h) => ({ ...acc, [h.id]: h }), {});
if (currentHistory) {
// The incoming history list contains less information than the current history
// so we restore the existing current history since it gets updated regularly anyway
newMap[currentHistoryId] = currentHistory;
}
// The incoming history list contains less information than the current history
// so we ensure that already available details are not getting lost
const enrichedHistories = newHistories.map((history) => {
const historyState = state.histories[history.id] || {};
return Object.assign({}, history, historyState);
});
const newMap = enrichedHistories.reduce((acc, h) => ({ ...acc, [h.id]: h }), {});
Vue.set(state, "histories", newMap);
},
setHistoriesLoading(state, isLoading) {
@@ -1,40 +0,0 @@
import { scrubModelProps } from "utils/safeAssign";
export class History {
constructor(doc = {}) {
try {
this.loadProps(doc);
} catch (err) {
console.debug("Unable to load history props.", doc);
throw err;
}
}
loadProps(raw = {}) {
Object.assign(this, raw);
}
clone() {
const newProps = cleanHistoryProps(this);
return new History(newProps);
}
patch(newProps) {
const cleanProps = cleanHistoryProps({ ...this, ...newProps });
return new History(cleanProps);
}
equals(other) {
return History.equals(this, other);
}
}
History.equals = function (a, b) {
return JSON.stringify(a) == JSON.stringify(b);
};
const scrubber = scrubModelProps(History);
export const cleanHistoryProps = (props = {}) => {
const cleanProps = JSON.parse(JSON.stringify(props));
return scrubber(cleanProps);
};
@@ -1,75 +0,0 @@
import { History } from "./History";
describe("History model", () => {
const initialProps = {
id: "abc123",
name: "Foo",
tags: ["a", "b", "c"],
annotation: "I am a test history",
};
const h = new History(initialProps);
describe("clone", () => {
const c = h.clone();
it("must be able to clone itself for use in editors", () => {
// should be == but not ===
expect(h).not.toBe(c);
expect(c).toBeInstanceOf(History);
expect(History.equals(h, c)).toBeTruthy();
});
it("should have the same props before and after cloning", () => {
expect(h.id).toEqual(c.id);
expect(h.name).toEqual(c.name);
expect(h.tags).toEqual(c.tags);
expect(h.annotation).toEqual(c.annotation);
});
});
describe("patch", () => {
const patchVals = {
name: "i was patched",
annotation: "patched annotation",
tags: ["foo", "bar", "blech"],
};
const clone = h.patch(patchVals);
it("should return a new instance of History", () => {
expect(h).not.toBe(clone);
expect(clone).toBeInstanceOf(History);
expect(History.equals(h, clone)).toBeFalsy();
});
it("should patch in passed properties", () => {
expect(clone.name).toEqual(patchVals.name);
expect(clone.annotation).toEqual(patchVals.annotation);
expect(clone.tags).toEqual(patchVals.tags);
});
});
describe("equivalence comparison", () => {
it("should recognize different props", () => {
const hDifferent = new History({ id: "def345" });
expect(History.equals(h, hDifferent)).toBeFalsy();
});
it("equivalence should work fine with cloned histories", () => {
const clone = h.clone();
// different object
expect(h).not.toBe(clone);
// but equivalent because props the same
expect(History.equals(h, clone)).toBeTruthy();
});
it("should work with patch", () => {
const clone = h.patch({ id: "floobar" });
// different object
expect(h).not.toBe(clone);
// not equivalent because different val
expect(History.equals(h, clone)).toBeFalsy();
});
});
});
+7 -16
View File
@@ -7,7 +7,6 @@
import axios from "axios";
import { prependPath } from "utils/redirect";
import { History } from "./History";
/**
* Generic json getter
@@ -47,7 +46,7 @@ const stdHistoryParams = {
*/
const extendedHistoryParams = {
view: "summary",
keys: "size,contents_active",
keys: "size,contents_active,user_id",
};
/**
@@ -56,8 +55,7 @@ const extendedHistoryParams = {
export async function getHistoryList() {
const url = "api/histories";
const response = await axios.get(prependPath(url), { params: stdHistoryParams });
const rawList = doResponse(response);
return rawList.map((props) => new History(props));
return doResponse(response);
}
/**
@@ -67,8 +65,7 @@ export async function getHistoryList() {
export async function getHistoryById(id) {
const path = `api/histories/${id}`;
const response = await axios.get(prependPath(path), { params: extendedHistoryParams });
const props = doResponse(response);
return new History(props);
return doResponse(response);
}
/**
@@ -87,8 +84,7 @@ export async function createNewHistory() {
if (!id) {
throw new Error("failed to create and select new history");
}
const newHistoryProps = doResponse(createResponse);
return new History(newHistoryProps);
return doResponse(createResponse);
}
/**
@@ -106,8 +102,7 @@ export async function cloneHistory(history, name, copyAll) {
current: true,
};
const response = await axios.post(prependPath(url), payload, { params: stdHistoryParams });
const clonedProps = doResponse(response);
return new History(clonedProps);
return doResponse(response);
}
/**
@@ -129,8 +124,7 @@ export async function deleteHistoryById(id, purge = false) {
export async function updateHistoryFields(id, payload) {
const url = `api/histories/${id}`;
const response = await axios.put(prependPath(url), payload, { params: extendedHistoryParams });
const props = doResponse(response);
return new History(props);
return doResponse(response);
}
/**
@@ -154,10 +148,7 @@ export async function secureHistory(history) {
export async function getCurrentHistoryFromServer(since) {
const url = "history/current_history_json";
const response = await axios.get(prependPath(url), { params: { since: since } });
const props = doResponse(response);
if (props) {
return new History(props);
}
return doResponse(response);
}
export async function setCurrentHistoryOnServer(history_id) {