Skip to content

Commit d76c7c2

Browse files
committed
paginate the history api call, load more functionality & ui
1 parent 70e17b4 commit d76c7c2

File tree

6 files changed

+151
-70
lines changed

6 files changed

+151
-70
lines changed

frontend/src/api/projectHistory.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import paginateUrl from '../utils/paginateUrl.js'
2+
13
import client from './client.js'
24

3-
const getHistory = async (instanceId) => {
4-
return await client.get(`/api/v1/projects/${instanceId}/history`)
5+
const getHistory = async (instanceId, cursor = undefined, limit = 10) => {
6+
const url = paginateUrl(`/api/v1/projects/${instanceId}/history`, cursor, limit)
7+
8+
return await client.get(url)
59
.then(res => {
610
return res.data
711
})

frontend/src/composables/Ux.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/**
2+
*
3+
* @param {Element} $el
4+
* @param {'auto' || 'instant' || 'smooth'} behavior
5+
* @param {number} top - Specifies the number of pixels along the Y axis to scroll the window or element.
6+
* @param {number} left - Specifies the number of pixels along the X axis to scroll the window or element.
7+
*/
8+
export function scrollTo ($el, {
9+
behavior = 'smooth',
10+
top = 0,
11+
left = 0
12+
} = {}) {
13+
$el.scrollTo({ behavior, top, left })
14+
}
15+
116
/**
217
*
318
* @param {Element} $el

frontend/src/pages/instance/VersionHistory/Timeline/components/TimelineEvent.vue

Lines changed: 80 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,75 @@
11
<template>
2-
<div class="event flex justify-between gap-1 items-center" :class="{'is-snapshot': isSnapshot}">
2+
<div
3+
class="event flex justify-between gap-1 items-center"
4+
:class="{'is-snapshot': isSnapshot, 'load-more': isLoadMore}"
5+
@click="loadMore"
6+
>
37
<timeline-graph :event="event" :timeline="timeline" />
48

5-
<div class="body flex flex-1 justify-between gap-2 items-center">
6-
<div class="content flex flex-1 flex-col justify-start">
7-
<component
8-
:is="title"
9-
class="title"
10-
@preview-snapshot="$emit('preview-snapshot', event.data.snapshot)"
11-
@preview-instance="openInstance"
12-
/>
13-
<div class="details">
14-
<span>{{ shortTitle }}</span> | <span>{{ createdAt }}</span>
9+
<template v-if="!isLoadMore">
10+
<div class="body flex flex-1 justify-between gap-2 items-center">
11+
<div class="content flex flex-1 flex-col justify-start">
12+
<component
13+
:is="title"
14+
class="title"
15+
@preview-snapshot="$emit('preview-snapshot', event.data.snapshot)"
16+
@preview-instance="openInstance"
17+
/>
18+
<div class="details">
19+
<span>{{ shortTitle }}</span> | <span>{{ createdAt }}</span>
20+
</div>
1521
</div>
22+
<div class="username">
23+
{{ event.user.name }}
24+
</div>
25+
</div>
26+
<div class="actions">
27+
<ff-kebab-menu v-if="snapshotExists" ref="kebab" menu-align="right">
28+
<ff-list-item
29+
:disabled="!hasPermission('project:snapshot:rollback')"
30+
label="Restore Snapshot"
31+
@click="$emit('restore-snapshot', event.data.snapshot)"
32+
/>
33+
<ff-list-item
34+
label="Edit Snapshot"
35+
:disabled="!hasPermission('snapshot:edit')"
36+
@click="$emit('edit-snapshot', event.data.snapshot)"
37+
/>
38+
<ff-list-item
39+
:disabled="!hasPermission('snapshot:full')"
40+
label="View Snapshot"
41+
@click="$emit('preview-snapshot', event.data.snapshot)"
42+
/>
43+
<ff-list-item
44+
:disabled="!hasPermission('project:snapshot:export')"
45+
label="Download Snapshot"
46+
@click="$emit('download-snapshot', event.data.snapshot)"
47+
/>
48+
<ff-list-item
49+
:disabled="!hasPermission('project:snapshot:read')"
50+
label="Download package.json"
51+
@click="$emit('download-package-json', event.data.snapshot)"
52+
/>
53+
<ff-list-item
54+
:disabled="!hasPermission('project:snapshot:set-target')"
55+
label="Set as Device Target"
56+
@click="$emit('set-device-target', event.data.snapshot)"
57+
/>
58+
<ff-list-item
59+
:disabled="!hasPermission('project:snapshot:delete')"
60+
label="Delete Snapshot"
61+
kind="danger"
62+
@click="$emit('delete-snapshot', event.data.snapshot)"
63+
/>
64+
</ff-kebab-menu>
1665
</div>
17-
<div class="username">
18-
{{ event.user.name }}
66+
</template>
67+
68+
<template v-else>
69+
<div class="body flex flex-1 justify-between gap-2 items-center cursor-pointer text-center">
70+
<h5 class="w-full">Load More</h5>
1971
</div>
20-
</div>
21-
<div class="actions">
22-
<ff-kebab-menu v-if="snapshotExists" ref="kebab" menu-align="right">
23-
<ff-list-item
24-
:disabled="!hasPermission('project:snapshot:rollback')"
25-
label="Restore Snapshot"
26-
@click="$emit('restore-snapshot', event.data.snapshot)"
27-
/>
28-
<ff-list-item
29-
label="Edit Snapshot"
30-
:disabled="!hasPermission('snapshot:edit')"
31-
@click="$emit('edit-snapshot', event.data.snapshot)"
32-
/>
33-
<ff-list-item
34-
:disabled="!hasPermission('snapshot:full')"
35-
label="View Snapshot"
36-
@click="$emit('preview-snapshot', event.data.snapshot)"
37-
/>
38-
<ff-list-item
39-
:disabled="!hasPermission('project:snapshot:export')"
40-
label="Download Snapshot"
41-
@click="$emit('download-snapshot', event.data.snapshot)"
42-
/>
43-
<ff-list-item
44-
:disabled="!hasPermission('project:snapshot:read')"
45-
label="Download package.json"
46-
@click="$emit('download-package-json', event.data.snapshot)"
47-
/>
48-
<ff-list-item
49-
:disabled="!hasPermission('project:snapshot:set-target')"
50-
label="Set as Device Target"
51-
@click="$emit('set-device-target', event.data.snapshot)"
52-
/>
53-
<ff-list-item
54-
:disabled="!hasPermission('project:snapshot:delete')"
55-
label="Delete Snapshot"
56-
kind="danger"
57-
@click="$emit('delete-snapshot', event.data.snapshot)"
58-
/>
59-
</ff-kebab-menu>
60-
</div>
72+
</template>
6173
</div>
6274
</template>
6375

@@ -92,7 +104,8 @@ export default {
92104
'download-package-json',
93105
'delete-snapshot',
94106
'edit-snapshot',
95-
'set-device-target'
107+
'set-device-target',
108+
'load-more'
96109
],
97110
computed: {
98111
createdAt () {
@@ -218,11 +231,17 @@ export default {
218231
},
219232
snapshotExists () {
220233
return this.isSnapshot && this.event.data.info.snapshotExists
234+
},
235+
isLoadMore () {
236+
return this.event.event === 'load-more'
221237
}
222238
},
223239
methods: {
224240
openInstance () {
225241
this.$router.push({ name: 'instance-overview', params: { id: this.event.data.sourceProject.id } })
242+
},
243+
loadMore () {
244+
if (this.isLoadMore) this.$emit('load-more')
226245
}
227246
}
228247
}
@@ -269,5 +288,11 @@ export default {
269288
background: $ff-grey-100;
270289
color: $ff-grey-500;
271290
}
291+
292+
&.load-more {
293+
background: $ff-grey-200;
294+
color: $ff-blue-500;
295+
cursor: pointer;
296+
}
272297
}
273298
</style>

frontend/src/pages/instance/VersionHistory/Timeline/components/TimelineGraph.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div class="graph" :class="{'is-snapshot': isSnapshot}">
3-
<span v-if="isSucceededBySnapshot && !isSnapshot" class="connector top snapshot" />
3+
<span v-if="isSucceededBySnapshot && (!isSnapshot || isLoadMore)" class="connector top snapshot" />
44
<span
55
v-if="hasSomethingToChainTo || (isSnapshot && !hasSomethingToChainTo && !isLastTimelineEvent)"
66
class="connector top"
@@ -17,7 +17,7 @@
1717
</template>
1818

1919
<script>
20-
import { AdjustmentsIcon, CameraIcon, DownloadIcon, PlusIcon } from '@heroicons/vue/outline'
20+
import { AdjustmentsIcon, CameraIcon, DotsVerticalIcon, DownloadIcon, PlusIcon } from '@heroicons/vue/outline'
2121
2222
import PipelinesIcon from '../../../../../components/icons/Pipelines.js'
2323
import ProjectsIcon from '../../../../../components/icons/Projects.js'
@@ -54,6 +54,8 @@ export default {
5454
return AdjustmentsIcon
5555
case this.event.event === 'project.created':
5656
return PlusIcon
57+
case this.event.event === 'load-more':
58+
return DotsVerticalIcon
5759
default:
5860
return null
5961
}
@@ -116,6 +118,9 @@ export default {
116118
},
117119
isLastTimelineEvent () {
118120
return this.isSucceededBy === undefined
121+
},
122+
isLoadMore () {
123+
return this.event.event === 'load-more'
119124
}
120125
}
121126
}

frontend/src/pages/instance/VersionHistory/Timeline/index.vue

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
<FeatureUnavailable v-if="!isTimelineFeatureEnabledForPlatform" />
44
<FeatureUnavailableToTeam v-else-if="!isTimelineFeatureEnabledForTeam" />
55
<section v-if="isTimelineFeatureEnabled" id="visual-timeline" class="relative" :style="{height: `${listHeight}px`}">
6-
<transition name="fade">
6+
<transition name="fade" mode="out-in">
77
<ff-loading v-if="loading" message="Loading Timeline..." class="absolute top-0" />
88

99
<!-- set mb-14 (~56px) on the form to permit access to kebab actions where hubspot chat covers it -->
1010
<!-- it's pb- in this case -->
11-
<ul v-else class="timeline pb-14 " :style="{'max-height': `${listHeight}px`}">
12-
<li v-for="event in timeline" :key="event.id">
11+
<ul v-else ref="timeline" class="timeline pb-14 " :style="{'max-height': `${listHeight}px`}">
12+
<li v-for="event in activeTimeline" :key="event.id">
1313
<timeline-event
1414
:event="event"
15-
:timeline="timeline"
15+
:timeline="activeTimeline"
1616
@preview-snapshot="showViewSnapshotDialog"
1717
@restore-snapshot="forceRefresh(showRollbackDialog, $event, true)"
1818
@compare-snapshot="forceRefresh(showCompareSnapshotDialog, $event)"
@@ -21,6 +21,7 @@
2121
@download-snapshot="showDownloadSnapshotDialog"
2222
@download-package-json="downloadSnapshotPackage"
2323
@set-device-target="showDeviceTargetDialog"
24+
@load-more="fetchData(true)"
2425
/>
2526
</li>
2627
</ul>
@@ -54,6 +55,7 @@ import FeatureUnavailable from '../../../../components/banners/FeatureUnavailabl
5455
import FeatureUnavailableToTeam from '../../../../components/banners/FeatureUnavailableToTeam.vue'
5556
import AssetDetailDialog from '../../../../components/dialogs/AssetDetailDialog.vue'
5657
import SnapshotEditDialog from '../../../../components/dialogs/SnapshotEditDialog.vue'
58+
import { scrollTo } from '../../../../composables/Ux.js'
5759
import featuresMixin from '../../../../mixins/Features.js'
5860
import snapshotsMixin from '../../../../mixins/Snapshots.js'
5961
import SnapshotExportDialog from '../../../application/Snapshots/components/dialogs/SnapshotExportDialog.vue'
@@ -87,7 +89,21 @@ export default {
8789
return {
8890
timeline: [],
8991
loading: false,
90-
listHeight: 0
92+
listHeight: 0,
93+
next_cursor: undefined
94+
}
95+
},
96+
computed: {
97+
canLoadMore () {
98+
return this.next_cursor !== undefined
99+
},
100+
activeTimeline () {
101+
if (this.canLoadMore) {
102+
return [...this.timeline, {
103+
event: 'load-more',
104+
id: 'load-more'
105+
}]
106+
} else return this.timeline
91107
}
92108
},
93109
watch: {
@@ -106,17 +122,28 @@ export default {
106122
window.removeEventListener('resize', this.computeTimelineListMaxHeight)
107123
},
108124
methods: {
109-
async fetchData () {
125+
async fetchData (loadMore = false) {
110126
if (this.isTimelineFeatureEnabled) {
111-
this.loading = true
127+
if (!loadMore) {
128+
this.loading = true
129+
}
112130
113131
// handling a specific scenario where users can navigate to the source snapshot instance, and when they click back,
114132
// we retrieve the timeline for that instance and display it for a short period of time
115133
if (this.instance.id && this.instance.id === this.$route.params.id) {
116-
projectHistoryAPI.getHistory(this.instance.id)
134+
projectHistoryAPI.getHistory(this.instance.id, this.next_cursor, 10)
117135
.then((response) => {
118136
this.loading = false
119-
this.timeline = response.timeline
137+
if (loadMore) {
138+
response.timeline.forEach(ev => {
139+
this.timeline.push(ev)
140+
this.$nextTick(() => scrollTo(this.$refs.timeline, {
141+
top: this.$refs.timeline.scrollHeight,
142+
behavior: 'smooth'
143+
}))
144+
})
145+
} else this.timeline = response.timeline
146+
this.next_cursor = response.meta.next_cursor
120147
})
121148
.catch(e => e)
122149
}

frontend/src/pages/instance/VersionHistory/index.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
</SectionTopMenu>
5252

5353
<router-view v-slot="{ Component }">
54-
<transition name="page-fade">
54+
<transition name="page-fade" mode="out-in">
5555
<component
5656
:is="Component"
5757
:instance="instance"
@@ -63,7 +63,12 @@
6363
</transition>
6464
</router-view>
6565

66-
<SnapshotCreateDialog ref="snapshotCreateDialog" data-el="dialog-create-snapshot" :project="instance" @snapshot-created="snapshotCreated" />
66+
<SnapshotCreateDialog
67+
ref="snapshotCreateDialog"
68+
data-el="dialog-create-snapshot"
69+
:project="instance"
70+
@snapshot-created="snapshotCreated"
71+
/>
6772
<SnapshotImportDialog
6873
ref="snapshotImportDialog"
6974
title="Upload Snapshot"

0 commit comments

Comments
 (0)