Split out loading of request and request details

On the requests page, only show details relating to the StorageRequested event
On the request details page, load more detailed information, including slot info. If already fetched, do not re-fetch
This commit is contained in:
Eric 2024-06-17 17:25:05 +10:00
parent a44f23719d
commit ed378e1a77
No known key found for this signature in database
12 changed files with 124 additions and 134 deletions

View File

@ -6,7 +6,7 @@ import Balance from '@/components/Balance.vue'
import BlockNumber from '@/components/BlockNumber.vue' import BlockNumber from '@/components/BlockNumber.vue'
import AppNav from '@/components/AppNav.vue' import AppNav from '@/components/AppNav.vue'
import ContractEventAlerts from '@/components/ContractEventAlerts.vue' import ContractEventAlerts from '@/components/ContractEventAlerts.vue'
import { initDrawers } from 'flowbite' import { initDrawers, initDismisses } from 'flowbite'
const alerts = ref([]) const alerts = ref([])
const id = ref(0) const id = ref(0)
@ -38,10 +38,11 @@ function addSlotAlert(type, event, state) {
onBeforeMount(async () => {}) onBeforeMount(async () => {})
onMounted(async () => { onMounted(async () => {
await requestsStore.fetch() await requestsStore.fetchPastRequests()
initDrawers() initDrawers()
initDismisses()
function onStorageRequested(blockNumber, requestId, request, state) { function onStorageRequested(blockNumber, requestId, state) {
alerts.value.push({ alerts.value.push({
type: 'info', type: 'info',
event: 'StorageRequested', event: 'StorageRequested',

View File

@ -1,16 +1,9 @@
<script setup> <script setup>
import { onMounted } from 'vue'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { initDrawers } from 'flowbite'
onMounted(() => {
initDrawers()
})
</script> </script>
<template> <template>
<nav class="mx-auto max-w-screen-xl flex flex-wrap items-center justify-between"> <nav class="mx-auto max-w-screen-xl flex flex-wrap items-center justify-between">
<!-- <div class=""> -->
<a href="https://codex.storage/" class="flex items-center rtl:space-x-reverse"> <a href="https://codex.storage/" class="flex items-center rtl:space-x-reverse">
<img src="../assets/logo.svg" class="h-8 hidden dark:inline" alt="Codex Logo" /> <img src="../assets/logo.svg" class="h-8 hidden dark:inline" alt="Codex Logo" />
<img src="../assets/logo-black.svg" class="h-8 inline dark:hidden" alt="Codex Logo" /> <img src="../assets/logo-black.svg" class="h-8 inline dark:hidden" alt="Codex Logo" />
@ -73,7 +66,6 @@ onMounted(() => {
</li> </li>
</ul> </ul>
</div> </div>
<!-- </div> -->
</nav> </nav>
</template> </template>

View File

@ -1,12 +1,8 @@
<script setup> <script setup>
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { initDismisses } from 'flowbite'
import AlertWithContent from '@/components/alerts/AlertWithContent.vue' import AlertWithContent from '@/components/alerts/AlertWithContent.vue'
const alerts = defineModel() const alerts = defineModel()
onMounted(() => {
initDismisses()
})
</script> </script>
<template> <template>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue' import { computed } from 'vue'
import { getRelativeTime } from '@feelinglovelynow/get-relative-time' import { getRelativeTime } from '@feelinglovelynow/get-relative-time'
import Tooltip from '@/components/Tooltip.vue' import Tooltip from '@/components/Tooltip.vue'
@ -9,14 +9,7 @@ const props = defineProps({
required: true required: true
} }
}) })
const date = ref(Date.now()) const relativeTime = computed(() => getRelativeTime(props.timestamp))
const relativeTime = ref(getRelativeTime(props.timestamp))
watch(date, () => (relativeTime.value = getRelativeTime(props.timestamp)))
let intervalId
// onMounted(() => {
// intervalId = setInterval(() => (date.value = Date.now()), 10000)
// })
// onUnmounted(() => clearInterval(intervalId))
</script> </script>
<template> <template>
<Tooltip> <Tooltip>

View File

@ -17,8 +17,9 @@ defineProps({
}) })
</script> </script>
<template> <template>
<Tooltip> {{shorten(value, ellipses, chars)}}
<!-- <Tooltip>
<template #text>{{ shorten(value, ellipses, chars) }}</template> <template #text>{{ shorten(value, ellipses, chars) }}</template>
<template #tooltip-content>{{ value }}</template> <template #tooltip-content>{{ value }}</template>
</Tooltip> </Tooltip> -->
</template> </template>

View File

@ -20,14 +20,13 @@ defineProps({
<tr> <tr>
<th scope="col" class="px-6 py-3">SlotID</th> <th scope="col" class="px-6 py-3">SlotID</th>
<th scope="col" class="px-6 py-3">Index</th> <th scope="col" class="px-6 py-3">Index</th>
<th scope="col" class="px-6 py-3">Proofs Missed</th>
<th scope="col" class="px-6 py-3">Provider</th> <th scope="col" class="px-6 py-3">Provider</th>
<th scope="col" class="px-6 py-3">State</th> <th scope="col" class="px-6 py-3">State</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr
v-for="({ slotId, slotIdx, state, proofsMissed, provider }, idx) in slots" v-for="({ slotId, slotIdx, state, provider }, idx) in slots"
:key="{ slotId }" :key="{ slotId }"
class="bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-600" class="bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-600"
> >
@ -38,7 +37,6 @@ defineProps({
<div class="text-base font-semibold">{{ shorten(slotId) }}</div> <div class="text-base font-semibold">{{ shorten(slotId) }}</div>
</th> </th>
<td class="px-6 py-4">{{ slotIdx }}</td> <td class="px-6 py-4">{{ slotIdx }}</td>
<td class="px-6 py-4">{{ proofsMissed }}</td>
<td class="px-6 py-4">{{ shorten(provider) }}</td> <td class="px-6 py-4">{{ shorten(provider) }}</td>
<td class="px-6 py-4"> <td class="px-6 py-4">
<div class="flex items-center"> <div class="flex items-center">

View File

@ -1,5 +1,6 @@
<script setup> <script setup>
import { computed } from 'vue' import { onMounted, computed } from 'vue'
import { initTooltips } from 'flowbite'
import { getStateColour, price } from '@/utils/requests' import { getStateColour, price } from '@/utils/requests'
import { autoPluralize } from '@/utils/strings' import { autoPluralize } from '@/utils/strings'
@ -20,6 +21,10 @@ const props = defineProps({
} }
}) })
onMounted(() => {
initTooltips()
})
const totalPrice = computed(() => price(props.request)) const totalPrice = computed(() => price(props.request))
const maxSlotLoss = computed(() => autoPluralize(props.request.ask.maxSlotLoss, 'slot')) const maxSlotLoss = computed(() => autoPluralize(props.request.ask.maxSlotLoss, 'slot'))
const slots = computed(() => autoPluralize(props.request.ask.slots, 'slot')) const slots = computed(() => autoPluralize(props.request.ask.slots, 'slot'))
@ -30,7 +35,7 @@ const stateColour = computed(() => getStateColour(props.request.state))
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<CodexImage <CodexImage
class="flex-initial mx-auto my-8 lg:my-16 min-w-sm max-w-md w-full rounded" class="flex-initial mx-auto my-8 lg:my-16 min-w-sm max-w-md w-full rounded"
:cid="request.content.cid" :cid="props.request.content.cid"
:local-only="!['New', 'Fulfilled'].includes(request.state)" :local-only="!['New', 'Fulfilled'].includes(request.state)"
></CodexImage> ></CodexImage>
<div class="py-8 px-4 ml-4 max-w-2xl lg:py-16 flex-1"> <div class="py-8 px-4 ml-4 max-w-2xl lg:py-16 flex-1">

View File

@ -1,14 +1,12 @@
<script setup> <script setup>
import { inject, ref, onMounted, computed } from 'vue' import { onMounted, computed } from 'vue'
import { initModals } from 'flowbite'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useRequestsStore } from '@/stores/requests' import { initTooltips } from 'flowbite'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import CodexImage from '@/components/CodexImage.vue' import { useRequestsStore } from '@/stores/requests'
import StateIndicator from '@/components/StateIndicator.vue' import StateIndicator from '@/components/StateIndicator.vue'
import RelativeTime from '@/components/RelativeTime.vue' import RelativeTime from '@/components/RelativeTime.vue'
import ShortenValue from '@/components/ShortenValue.vue' import ShortenValue from '@/components/ShortenValue.vue'
import { shorten } from '@/utils/ids'
import { getStateColour } from '@/utils/requests' import { getStateColour } from '@/utils/requests'
const requestsStore = useRequestsStore() const requestsStore = useRequestsStore()
@ -20,6 +18,9 @@ const requestsOrdered = computed(() => {
) )
return sorted return sorted
}) })
onMounted(() => {
initTooltips()
})
</script> </script>
<template> <template>
@ -132,14 +133,13 @@ const requestsOrdered = computed(() => {
> >
<tr> <tr>
<th scope="col" class="px-6 py-3">RequestID</th> <th scope="col" class="px-6 py-3">RequestID</th>
<th scope="col" class="px-6 py-3">CID</th>
<th scope="col" class="px-6 py-3">State</th> <th scope="col" class="px-6 py-3">State</th>
<th scope="col" class="px-6 py-3">Action</th> <th scope="col" class="px-6 py-3">Action</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr
v-for="([requestId, { requestedAt, content, state }], idx) in requestsOrdered" v-for="([requestId, { requestedAt, state }], idx) in requestsOrdered"
:key="{ requestId }" :key="{ requestId }"
class="cursor-pointer bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-600" class="cursor-pointer bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-600"
@click="router.push(`/request/${requestId}`)" @click="router.push(`/request/${requestId}`)"
@ -148,11 +148,6 @@ const requestsOrdered = computed(() => {
scope="row" scope="row"
class="flex items-center px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white" class="flex items-center px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white"
> >
<CodexImage
:local-only="!['New', 'Fulfilled'].includes(state)"
:cid="content.cid"
class="w-10 h-10 rounded-full mt-1"
/>
<div class="ps-3"> <div class="ps-3">
<div class="text-base font-semibold"> <div class="text-base font-semibold">
<ShortenValue :value="requestId"></ShortenValue> <ShortenValue :value="requestId"></ShortenValue>
@ -162,16 +157,12 @@ const requestsOrdered = computed(() => {
</div> </div>
</div> </div>
</th> </th>
<td class="px-6 py-4">
<ShortenValue :value="content.cid"></ShortenValue>
</td>
<td class="px-6 py-4"> <td class="px-6 py-4">
<div class="flex items-center"> <div class="flex items-center">
<StateIndicator :text="state" :color="getStateColour(state)"></StateIndicator> <StateIndicator :text="state" :color="getStateColour(state)"></StateIndicator>
</div> </div>
</td> </td>
<td class="px-6 py-4"> <td class="px-6 py-4">
<!-- Modal toggle -->
<a <a
href="#" href="#"
type="button" type="button"

View File

@ -1,19 +1,11 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted } from 'vue'
import { initTooltips } from 'flowbite'
// defineProps({
// id: {
// type: String,
// required: true
// }
// })
onMounted(() => { onMounted(() => {
initTooltips()
}) })
</script> </script>
<template> <template>
<!-- <slot :id="$idRef('tooltip')" name="text"></slot> -->
<p :data-tooltip-target="$id('tooltip')" class="cursor-help"> <p :data-tooltip-target="$id('tooltip')" class="cursor-help">
<slot name="text"></slot> <slot name="text"></slot>
</p> </p>

View File

@ -63,14 +63,20 @@ export const useRequestsStore = defineStore('request', () => {
} }
const getSlots = async (requestId, numSlots) => { const getSlots = async (requestId, numSlots) => {
console.log(`fetching ${numSlots} slots`)
let start = Date.now()
let slots = [] let slots = []
for (let slotIdx = 0; slotIdx < numSlots; slotIdx++) { for (let slotIdx = 0; slotIdx < numSlots; slotIdx++) {
let id = slotId(requestId, slotIdx) let id = slotId(requestId, slotIdx)
const startSlotState = Date.now()
let state = await getSlotState(id) let state = await getSlotState(id)
let proofsMissed = await marketplace.missingProofs(id) console.log(`fetched slot state in ${(Date.now() - startSlotState) / 1000}s`)
const startGetHost = Date.now()
let provider = await marketplace.getHost(id) let provider = await marketplace.getHost(id)
slots.push({ slotId: id, slotIdx, state, proofsMissed, provider }) console.log(`fetched slot provider in ${(Date.now() - startGetHost) / 1000}s`)
slots.push({ slotId: id, slotIdx, state, provider })
} }
console.log(`fetched ${numSlots} slots in ${(Date.now() - start) / 1000}s`)
return slots return slots
// blockNumbers.value.add(blockNumber) // blockNumbers.value.add(blockNumber)
} }
@ -85,40 +91,34 @@ export const useRequestsStore = defineStore('request', () => {
} }
} }
async function fetch() { async function addRequest(requestId, blockHash) {
let state = await getRequestState(requestId)
let block = await getBlock(blockHash)
let reqExisting = requests.value.get(requestId) // just in case it already exists
let request = {
...reqExisting,
state,
requestedAt: block.timestamp,
requestFinishedId: null,
detailsFetched: false
}
requests.value.set(requestId, request)
return request
}
async function fetchPastRequests() {
// query past events // query past events
console.log('fetching past requests')
loading.value = true loading.value = true
try { try {
let events = await marketplace.queryFilter(StorageRequested) let events = await marketplace.queryFilter(StorageRequested)
console.log('got ', events.length, ' StorageRequested events') console.log('got ', events.length, ' StorageRequested events')
let reqs = new Map()
events.forEach(async (event, i) => { events.forEach(async (event, i) => {
console.log('getting details for StorageRequested event ', i) console.log('getting details for StorageRequested event ', i)
let start = Date.now() let start = Date.now()
// let event = events[i]
// await events.forEach(async (event) => {
let { requestId, ask, expiry } = event.args let { requestId, ask, expiry } = event.args
let { blockHash, blockNumber } = event let { blockHash, blockNumber } = event
let arrRequest = await marketplace.getRequest(requestId) await addRequest(requestId, blockHash)
let request = arrayToObject(arrRequest)
let state = await getRequestState(requestId)
let slots = await getSlots(requestId, request.ask.slots)
let block = await getBlock(blockHash)
// populate temp map to constrain state update volume
// reqs.set(requestId, {
// ...request,
// state,
// slots: [],
// requestedAt: block.timestamp,
// requestFinishedId: null
// })
requests.value.set(requestId, {
...request,
state,
slots,
requestedAt: block.timestamp,
requestFinishedId: null
})
console.log(`got details for ${i} in ${(Date.now() - start) / 1000} seconds`) console.log(`got details for ${i} in ${(Date.now() - start) / 1000} seconds`)
if (i === events.length - 1) { if (i === events.length - 1) {
loading.value = false loading.value = false
@ -127,32 +127,59 @@ export const useRequestsStore = defineStore('request', () => {
if (events.length === 0) { if (events.length === 0) {
loading.value = false loading.value = false
} }
// reqs.forEach((request, requestId) => requests.value.set(requestId, request))
} catch (error) { } catch (error) {
console.error(`failed to load past contract events: ${error.message}`) console.error(`failed to load past contract events: ${error.message}`)
} }
} }
async function fetchRequest(requestId) {
let start = Date.now()
console.log('fetching request ', requestId)
const preFetched = requests.value.get(requestId)
if (preFetched?.detailsFetched) {
return
}
loading.value = true
try {
let arrRequest = await marketplace.getRequest(requestId)
let request = arrayToObject(arrRequest)
let slots = await getSlots(requestId, request.ask.slots)
const reqExisting = requests.value.get(requestId)
requests.value.set(requestId, {
...reqExisting, // state, requestedAt, requestFinishedId (null)
...request,
slots,
detailsFetched: true
})
} catch (error) {
console.error(`failed to load slots for request ${requestId}: ${error}`)
throw error
} finally {
console.log(`fetched request in ${(Date.now() - start) / 1000}s`)
loading.value = false
}
}
function updateRequestState(requestId, newState) { function updateRequestState(requestId, newState) {
let { request, state, slots, requestFinishedId } = requests.value.get(requestId) let { state, ...rest } = requests.value.get(requestId)
state = newState state = newState
requests.value.set(requestId, { request, state, slots, requestFinishedId }) requests.value.set(requestId, { state, ...rest })
} }
function updateRequestFinishedId(requestId, newRequestFinishedId) { function updateRequestFinishedId(requestId, newRequestFinishedId) {
let { request, state, slots, requestFinishedId } = requests.value.get(requestId) let { requestFinishedId, ...rest } = requests.value.get(requestId)
requestFinishedId = newRequestFinishedId requestFinishedId = newRequestFinishedId
requests.value.set(requestId, { request, state, slots, requestFinishedId }) requests.value.set(requestId, { requestFinishedId, ...rest })
} }
function updateRequestSlotState(requestId, slotIdx, newState) { function updateRequestSlotState(requestId, slotIdx, newState) {
let { request, state, slots, requestFinishedId } = requests.value.get(requestId) let { slots, ...rest } = requests.value.get(requestId)
slots = slots.map((slot) => { slots = slots.map((slot) => {
if (slot.slotIdx == slotIdx) { if (slot.slotIdx == slotIdx) {
slot.state = newState slot.state = newState
} }
}) })
requests.value.set(requestId, { request, state, slots, requestFinishedId }) requests.value.set(requestId, { slots, ...rest })
} }
function waitForRequestFinished(requestId, duration, onRequestFinished) { function waitForRequestFinished(requestId, duration, onRequestFinished) {
@ -184,22 +211,11 @@ export const useRequestsStore = defineStore('request', () => {
) { ) {
marketplace.on(StorageRequested, async (requestId, ask, expiry, event) => { marketplace.on(StorageRequested, async (requestId, ask, expiry, event) => {
let { blockNumber, blockHash } = event.log let { blockNumber, blockHash } = event.log
let arrRequest = await marketplace.getRequest(requestId) const request = addRequest(requestId, blockHash)
let request = arrayToObject(arrRequest)
let state = await getRequestState(requestId)
let slots = await getSlots(requestId, request.ask.slots)
let block = await getBlock(blockHash)
requests.value.set(requestId, {
...request,
state,
slots,
requestedAt: block.timestamp,
requestFinishedId: null
})
// callback // callback
if (onStorageRequested) { if (onStorageRequested) {
onStorageRequested(blockNumber, requestId, request, state) onStorageRequested(blockNumber, requestId, request.state)
} }
}) })
@ -254,11 +270,6 @@ export const useRequestsStore = defineStore('request', () => {
}) })
} }
// const eventsByBlock = computed(() =>)
// const doubleCount = computed(() => count.value * 2)
// function increment() {
// count.value++
// }
return { return {
requests, requests,
// slots, // slots,
@ -270,7 +281,8 @@ export const useRequestsStore = defineStore('request', () => {
// requestCancelledEvents, // requestCancelledEvents,
// requestFailedEvents, // requestFailedEvents,
// requestFinishedEvents, // requestFinishedEvents,
fetch, fetchPastRequests,
fetchRequest,
listenForNewEvents, listenForNewEvents,
loading loading
} }

View File

@ -10,32 +10,43 @@ const requestsStore = useRequestsStore()
const { requests, loading } = storeToRefs(requestsStore) const { requests, loading } = storeToRefs(requestsStore)
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const isLoading = computed(() => loading.value) const request = ref(undefined)
const requestId = ref(route.params.requestId)
const request = ref(requests?.value?.get(requestId.value)) async function fetch(requestId) {
const requestNotFound = computed( try {
() => await requestsStore.fetchRequest(requestId)
!isLoading.value && requests.value !== undefined && !requests.value.get(route.params.requestId) } catch (error) {
) if (error.message.includes('Unknown request')) {
function getRequestFromStore(_) { router.push({ name: 'NotFound' })
let req = requests?.value?.get(route.params.requestId) }
if (requestNotFound.value) {
router.push({ name: 'NotFound' })
} else {
request.value = req
} }
request.value = requests.value.get(requestId)
}
const hasRequest = computed(() => {
return request.value !== undefined
})
watch(() => route.params.requestId, fetch)
if (loading.value) {
watch(
() => loading.value,
(isLoading) => {
if (!isLoading) {
fetch(route.params.requestId)
}
},
{ once: true }
)
} else {
fetch(route.params.requestId)
} }
watch(() => route.params.requestId, getRequestFromStore)
watch(() => isLoading.value, getRequestFromStore)
</script> </script>
<template> <template>
<div> <div>
<SkeletonLoading v-if="isLoading" type="image" /> <SkeletonLoading v-if="loading" type="image" />
<StorageRequest <StorageRequest v-else-if="hasRequest" :requestId="route.params.requestId" :request="request" />
v-else-if="!requestNotFound"
:requestId="route.params.requestId"
:request="request"
/>
</div> </div>
</template> </template>

View File

@ -3,16 +3,14 @@ import { storeToRefs } from 'pinia'
import { useRequestsStore } from '@/stores/requests' import { useRequestsStore } from '@/stores/requests'
import StorageRequests from '@/components/StorageRequests.vue' import StorageRequests from '@/components/StorageRequests.vue'
import SkeletonLoading from '@/components/SkeletonLoading.vue' import SkeletonLoading from '@/components/SkeletonLoading.vue'
import { computed } from 'vue'
const requestsStore = useRequestsStore() const requestsStore = useRequestsStore()
const { loading, requests } = storeToRefs(requestsStore) const { loading } = storeToRefs(requestsStore)
const isLoading = computed(() => loading.value || !requests.value)
</script> </script>
<template> <template>
<div> <div>
<SkeletonLoading v-if="isLoading" type="text" /> <SkeletonLoading v-if="loading" type="text" />
<StorageRequests v-else /> <StorageRequests v-else />
</div> </div>
</template> </template>