diff --git a/client/src/components/Common/TextSummary.vue b/client/src/components/Common/TextSummary.vue index 277a46e07007..de9263f3687e 100644 --- a/client/src/components/Common/TextSummary.vue +++ b/client/src/components/Common/TextSummary.vue @@ -5,14 +5,32 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { computed } from "vue"; const props = defineProps({ + /** The text to summarize */ description: { type: String, required: true, }, + /** If `true`, doesn't let unexpanded text go beyond height of one line */ + oneLineSummary: { + type: Boolean, + default: false, + }, + /** If `true`, doesn't show expand/collapse buttons */ + noExpand: { + type: Boolean, + default: false, + }, + /** The component to use for the summary, default = `

` */ + component: { + type: String, + default: "p", + }, + /** Used as the toggle for expanding summary */ showDetails: { type: Boolean, default: false, }, + /** The maximum length of the unexpanded text / summary */ maxLength: { type: Number, default: 150, @@ -47,8 +65,11 @@ const text = computed(() => + + diff --git a/client/src/components/History/CurrentHistory/HistoryDetails.vue b/client/src/components/History/CurrentHistory/HistoryDetails.vue index 075100cc7517..fc38695c49e8 100644 --- a/client/src/components/History/CurrentHistory/HistoryDetails.vue +++ b/client/src/components/History/CurrentHistory/HistoryDetails.vue @@ -4,10 +4,26 @@ :annotation="history.annotation" :tags="history.tags" :writeable="writeable" + :summarized="summarized" + :update-time="history.update_time" @save="onSave"> + @@ -18,11 +34,15 @@ import { mapActions } from "pinia"; import short from "@/components/plugins/short.js"; import { useHistoryStore } from "@/stores/historyStore"; +import TextSummary from "@/components/Common/TextSummary.vue"; import DetailsLayout from "@/components/History/Layout/DetailsLayout.vue"; +import UtcDate from "@/components/UtcDate.vue"; export default { components: { DetailsLayout, + TextSummary, + UtcDate, }, directives: { short, @@ -30,6 +50,7 @@ export default { props: { history: { type: Object, required: true }, writeable: { type: Boolean, default: true }, + summarized: { type: Boolean, default: false }, }, methods: { ...mapActions(useHistoryStore, ["updateHistory"]), diff --git a/client/src/components/History/CurrentHistory/HistoryPanel.vue b/client/src/components/History/CurrentHistory/HistoryPanel.vue index e335cfd8c709..496465b0fee2 100644 --- a/client/src/components/History/CurrentHistory/HistoryPanel.vue +++ b/client/src/components/History/CurrentHistory/HistoryPanel.vue @@ -38,7 +38,11 @@ :search-error="searchError" :show-advanced.sync="showAdvanced" />

- + -
+
-
- +
+
+ +
+ +
@@ -72,11 +94,13 @@ import { mapState } from "pinia"; import short from "@/components/plugins/short.js"; import { useUserStore } from "@/stores/userStore"; +import TextSummary from "@/components/Common/TextSummary.vue"; import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue"; export default { components: { StatelessTags, + TextSummary, }, directives: { short, @@ -85,6 +109,7 @@ export default { name: { type: String, default: null }, annotation: { type: String, default: null }, showAnnotation: { type: Boolean, default: true }, + summarized: { type: Boolean, default: false }, tags: { type: Array, default: null }, writeable: { type: Boolean, default: true }, }, @@ -140,3 +165,14 @@ export default { }, }; + + diff --git a/client/src/components/History/Modals/SelectorModal.vue b/client/src/components/History/Modals/SelectorModal.vue index 00052ea4e50a..98311f6906dc 100644 --- a/client/src/components/History/Modals/SelectorModal.vue +++ b/client/src/components/History/Modals/SelectorModal.vue @@ -5,6 +5,7 @@ import { faArrowDown, faColumns, faSignInAlt } from "@fortawesome/free-solid-svg import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { useInfiniteScroll } from "@vueuse/core"; import { BBadge, BButton, BButtonGroup, BFormGroup, BListGroup, BListGroupItem, BModal } from "bootstrap-vue"; +import { orderBy } from "lodash"; import isEqual from "lodash.isequal"; import { storeToRefs } from "pinia"; import { computed, onMounted, onUnmounted, type PropType, type Ref, ref, watch } from "vue"; @@ -72,10 +73,9 @@ const modal: Ref = ref(null); const scrollableDiv: Ref = ref(null); const historyStore = useHistoryStore(); -const { currentHistoryId, totalHistoryCount } = storeToRefs(useHistoryStore()); +const { currentHistoryId, totalHistoryCount, pinnedHistories } = storeToRefs(useHistoryStore()); const { currentUser } = storeToRefs(useUserStore()); -const pinnedHistories: Ref<{ id: string }[]> = computed(() => historyStore.pinnedHistories); const hasNoResults = computed(() => filter.value && filtered.value.length == 0); const validFilter = computed(() => filter.value && filter.value.length > 2); const allLoaded = computed(() => totalHistoryCount.value <= filtered.value.length); @@ -153,6 +153,12 @@ const filtered: Ref = computed(() => { }); }); +/** if pinned histories and selected histories are equal */ +const pinnedSelectedEqual = computed(() => { + // uses `orderBy` to ensure same ids are found in both `{ id: string }[]` arrays + return isEqual(orderBy(pinnedHistories.value, ["id"], ["asc"]), orderBy(selectedHistories.value, ["id"], ["asc"])); +}); + function historyClicked(history: HistorySummary) { if (props.multiple) { const index = selectedHistories.value.findIndex((item) => item.id == history.id); @@ -249,10 +255,18 @@ async function loadMore(noScroll = false) { :active="selectedHistories.some((h) => h.id === history.id)" @click="() => historyClicked(history)">
- - {{ history.name }} - (Current) - +
+ + {{ history.name }} + (Current) + + + (currently pinned) + +
@@ -333,10 +347,11 @@ async function loadMore(noScroll = false) {
+ {{ selectedHistories.length }} histories selected Change Selected diff --git a/client/src/components/History/Multiple/MultipleView.test.js b/client/src/components/History/Multiple/MultipleView.test.js index 13fa2ff4948b..6175c7c69bd1 100644 --- a/client/src/components/History/Multiple/MultipleView.test.js +++ b/client/src/components/History/Multiple/MultipleView.test.js @@ -10,55 +10,76 @@ import { getLocalVue } from "tests/jest/helpers"; import MultipleView from "./MultipleView"; -const COUNT = 8; const USER_ID = "test-user-id"; -const CURRENT_HISTORY_ID = "test-history-id-0"; - -const pinia = createPinia(); +const FIRST_HISTORY_ID = "test-history-id-0"; const getFakeHistorySummaries = (num, selectedIndex) => { return Array.from({ length: num }, (_, index) => ({ - id: selectedIndex === index ? CURRENT_HISTORY_ID : `test-history-id-${index}`, + id: `test-history-id-${index}`, name: `History-${index}`, tags: [], update_time: new Date().toISOString(), })); }; const currentUser = { id: USER_ID }; -const UserHistoriesMock = MockUserHistories({ id: CURRENT_HISTORY_ID }, getFakeHistorySummaries(COUNT, 0), false); - -const localVue = getLocalVue(); describe("MultipleView", () => { - let wrapper; - let axiosMock; - - beforeEach(async () => { - axiosMock = new MockAdapter(axios); - wrapper = mount(MultipleView, { - pinia, + async function setUpWrapper(UserHistoriesMock, count, currentHistoryId) { + const axiosMock = new MockAdapter(axios); + axiosMock.onGet(`api/histories/${FIRST_HISTORY_ID}`).reply(200, {}); + const wrapper = mount(MultipleView, { + pinia: createPinia(), stubs: { UserHistories: UserHistoriesMock, HistoryPanel: true, + icon: { template: "
" }, }, - localVue, + localVue: getLocalVue(), }); const userStore = useUserStore(); userStore.currentUser = currentUser; const historyStore = useHistoryStore(); - historyStore.setHistories(getFakeHistorySummaries(COUNT, 0)); - historyStore.setCurrentHistoryId(CURRENT_HISTORY_ID); + historyStore.setHistories(getFakeHistorySummaries(count, 0)); + historyStore.setCurrentHistoryId(currentHistoryId); await flushPromises(); - }); - afterEach(() => { + return { wrapper, axiosMock }; + } + + it("more than 4 histories should not show the current history", async () => { + const count = 8; + const currentHistoryId = FIRST_HISTORY_ID; + + // Set up UserHistories and wrapper + const UserHistoriesMock = MockUserHistories({ id: currentHistoryId }, getFakeHistorySummaries(count, 0), false); + const { wrapper, axiosMock } = await setUpWrapper(UserHistoriesMock, count, currentHistoryId); + + // Test: current (first) history should not be shown because only 4 latest are shown by default + expect(wrapper.find("button[title='Current History']").exists()).toBeFalsy(); + + expect(wrapper.find("button[title='Switch to this history']").exists()).toBeTruthy(); + + expect(wrapper.find("div[title='Currently showing 4 most recently updated histories']").exists()).toBeTruthy(); + + expect(wrapper.find("[data-description='open select histories modal']").exists()).toBeTruthy(); + axiosMock.reset(); }); - it("should show the current history", async () => { + it("less than or equal to 4 histories should not show the current history", async () => { + const count = 3; + const currentHistoryId = FIRST_HISTORY_ID; + + // Set up UserHistories and wrapper + const UserHistoriesMock = MockUserHistories({ id: currentHistoryId }, getFakeHistorySummaries(count, 0), false); + const { wrapper, axiosMock } = await setUpWrapper(UserHistoriesMock, count, currentHistoryId); + + // Test: current (first) history should be shown because only 4 latest are shown by default, and count = 3 expect(wrapper.find("button[title='Current History']").exists()).toBeTruthy(); + + axiosMock.reset(); }); }); diff --git a/client/src/components/History/Multiple/MultipleView.vue b/client/src/components/History/Multiple/MultipleView.vue index 048efe383dc9..9b2c5c92208f 100644 --- a/client/src/components/History/Multiple/MultipleView.vue +++ b/client/src/components/History/Multiple/MultipleView.vue @@ -1,10 +1,11 @@