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

View File

@ -1,16 +1,9 @@
<script setup>
import { onMounted } from 'vue'
import { RouterLink } from 'vue-router'
import { initDrawers } from 'flowbite'
onMounted(() => {
initDrawers()
})
</script>
<template>
<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">
<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" />
@ -73,7 +66,6 @@ onMounted(() => {
</li>
</ul>
</div>
<!-- </div> -->
</nav>
</template>

View File

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

View File

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

View File

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

View File

@ -20,14 +20,13 @@ defineProps({
<tr>
<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">Proofs Missed</th>
<th scope="col" class="px-6 py-3">Provider</th>
<th scope="col" class="px-6 py-3">State</th>
</tr>
</thead>
<tbody>
<tr
v-for="({ slotId, slotIdx, state, proofsMissed, provider }, idx) in slots"
v-for="({ slotId, slotIdx, state, provider }, idx) in slots"
:key="{ slotId }"
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>
</th>
<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">
<div class="flex items-center">

View File

@ -1,5 +1,6 @@
<script setup>
import { computed } from 'vue'
import { onMounted, computed } from 'vue'
import { initTooltips } from 'flowbite'
import { getStateColour, price } from '@/utils/requests'
import { autoPluralize } from '@/utils/strings'
@ -20,6 +21,10 @@ const props = defineProps({
}
})
onMounted(() => {
initTooltips()
})
const totalPrice = computed(() => price(props.request))
const maxSlotLoss = computed(() => autoPluralize(props.request.ask.maxSlotLoss, '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">
<CodexImage
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)"
></CodexImage>
<div class="py-8 px-4 ml-4 max-w-2xl lg:py-16 flex-1">

View File

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

View File

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

View File

@ -63,14 +63,20 @@ export const useRequestsStore = defineStore('request', () => {
}
const getSlots = async (requestId, numSlots) => {
console.log(`fetching ${numSlots} slots`)
let start = Date.now()
let slots = []
for (let slotIdx = 0; slotIdx < numSlots; slotIdx++) {
let id = slotId(requestId, slotIdx)
const startSlotState = Date.now()
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)
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
// 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
console.log('fetching past requests')
loading.value = true
try {
let events = await marketplace.queryFilter(StorageRequested)
console.log('got ', events.length, ' StorageRequested events')
let reqs = new Map()
events.forEach(async (event, i) => {
console.log('getting details for StorageRequested event ', i)
let start = Date.now()
// let event = events[i]
// await events.forEach(async (event) => {
let { requestId, ask, expiry } = event.args
let { blockHash, blockNumber } = event
let arrRequest = await marketplace.getRequest(requestId)
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
})
await addRequest(requestId, blockHash)
console.log(`got details for ${i} in ${(Date.now() - start) / 1000} seconds`)
if (i === events.length - 1) {
loading.value = false
@ -127,32 +127,59 @@ export const useRequestsStore = defineStore('request', () => {
if (events.length === 0) {
loading.value = false
}
// reqs.forEach((request, requestId) => requests.value.set(requestId, request))
} catch (error) {
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) {
let { request, state, slots, requestFinishedId } = requests.value.get(requestId)
let { state, ...rest } = requests.value.get(requestId)
state = newState
requests.value.set(requestId, { request, state, slots, requestFinishedId })
requests.value.set(requestId, { state, ...rest })
}
function updateRequestFinishedId(requestId, newRequestFinishedId) {
let { request, state, slots, requestFinishedId } = requests.value.get(requestId)
let { requestFinishedId, ...rest } = requests.value.get(requestId)
requestFinishedId = newRequestFinishedId
requests.value.set(requestId, { request, state, slots, requestFinishedId })
requests.value.set(requestId, { requestFinishedId, ...rest })
}
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) => {
if (slot.slotIdx == slotIdx) {
slot.state = newState
}
})
requests.value.set(requestId, { request, state, slots, requestFinishedId })
requests.value.set(requestId, { slots, ...rest })
}
function waitForRequestFinished(requestId, duration, onRequestFinished) {
@ -184,22 +211,11 @@ export const useRequestsStore = defineStore('request', () => {
) {
marketplace.on(StorageRequested, async (requestId, ask, expiry, event) => {
let { blockNumber, blockHash } = event.log
let arrRequest = await marketplace.getRequest(requestId)
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
})
const request = addRequest(requestId, blockHash)
// callback
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 {
requests,
// slots,
@ -270,7 +281,8 @@ export const useRequestsStore = defineStore('request', () => {
// requestCancelledEvents,
// requestFailedEvents,
// requestFinishedEvents,
fetch,
fetchPastRequests,
fetchRequest,
listenForNewEvents,
loading
}

View File

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

View File

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