Update client lib data fetch models to support generic pagination

This commit is contained in:
Andrew Kingston 2021-12-17 18:39:48 +00:00
parent 882067056c
commit 441cc2fccf
7 changed files with 99 additions and 62 deletions

View File

@ -4,7 +4,7 @@ import API from "./api"
/** /**
* Executes a query against an external data connector. * Executes a query against an external data connector.
*/ */
export const executeQuery = async ({ queryId, parameters }) => { export const executeQuery = async ({ queryId, pagination, parameters }) => {
const query = await fetchQueryDefinition(queryId) const query = await fetchQueryDefinition(queryId)
if (query?.datasourceId == null) { if (query?.datasourceId == null) {
notificationStore.actions.error("That query couldn't be found") notificationStore.actions.error("That query couldn't be found")
@ -14,6 +14,7 @@ export const executeQuery = async ({ queryId, parameters }) => {
url: `/api/v2/queries/${queryId}`, url: `/api/v2/queries/${queryId}`,
body: { body: {
parameters, parameters,
pagination,
}, },
}) })
if (res.error) { if (res.error) {

View File

@ -12,7 +12,7 @@ import { fetchTableDefinition } from "api"
* internal table or datasource plus. * internal table or datasource plus.
* For other types of datasource, this class is overridden and extended. * For other types of datasource, this class is overridden and extended.
*/ */
export default class TableFetch { export default class DataFetch {
// Feature flags // Feature flags
supportsSearch = false supportsSearch = false
supportsSort = false supportsSort = false
@ -21,7 +21,6 @@ export default class TableFetch {
// Config // Config
options = { options = {
datasource: null, datasource: null,
schema: null,
limit: 10, limit: 10,
// Search config // Search config
@ -53,23 +52,20 @@ export default class TableFetch {
/** /**
* Constructs a new DataFetch instance. * Constructs a new DataFetch instance.
* @param opts the fetch options * @param opts the fetch options
* @param flags the datasource feature flags
*/ */
constructor(opts, flags) { constructor(opts) {
// Merge options with their default values // Merge options with their default values
this.options = { this.options = {
...this.options, ...this.options,
...opts, ...opts,
} }
// Update feature flags
this.supportsSearch = flags?.supportsSearch || false
this.supportsSort = flags?.supportsSort || false
this.supportsPagination = flags?.supportsPagination || false
// Bind all functions to properly scope "this" // Bind all functions to properly scope "this"
this.getData = this.getData.bind(this) this.getData = this.getData.bind(this)
this.getPage = this.getPage.bind(this)
this.getInitialData = this.getInitialData.bind(this) this.getInitialData = this.getInitialData.bind(this)
this.determineFeatureFlags = this.determineFeatureFlags.bind(this)
this.enrichSchema = this.enrichSchema.bind(this)
this.refresh = this.refresh.bind(this) this.refresh = this.refresh.bind(this)
this.update = this.update.bind(this) this.update = this.update.bind(this)
this.hasNextPage = this.hasNextPage.bind(this) this.hasNextPage = this.hasNextPage.bind(this)
@ -116,11 +112,13 @@ export default class TableFetch {
return return
} }
// Ensure schema exists and enrich it // Fetch datasource definition and determine feature flags
let schema = this.options.schema const definition = await this.constructor.getDefinition(datasource)
if (!schema) { this.determineFeatureFlags(definition)
schema = await this.constructor.getSchema(datasource)
} // Fetch and enrich schema
let schema = this.constructor.getSchema(datasource, definition)
schema = this.enrichSchema(schema)
if (!schema) { if (!schema) {
return return
} }
@ -142,13 +140,16 @@ export default class TableFetch {
} }
// Update store // Update store
this.store.update($store => ({ ...$store, schema, query, loading: true })) this.store.update($store => ({
...$store,
definition,
schema,
query,
loading: true,
}))
// Actually fetch data // Actually fetch data
const page = await this.getPage() const page = await this.getPage()
if (page.info) {
console.log("new info", page.info)
}
this.store.update($store => ({ this.store.update($store => ({
...$store, ...$store,
loading: false, loading: false,
@ -165,7 +166,7 @@ export default class TableFetch {
*/ */
async getPage() { async getPage() {
const { sortColumn, sortOrder, sortType, limit } = this.options const { sortColumn, sortOrder, sortType, limit } = this.options
const { query } = get(this.store) const { query, pageNumber } = get(this.store)
// Get the actual data // Get the actual data
let { rows, info, hasNextPage, cursor } = await this.getData() let { rows, info, hasNextPage, cursor } = await this.getData()
@ -182,7 +183,8 @@ export default class TableFetch {
// If we don't support pagination, do a client-side limit // If we don't support pagination, do a client-side limit
if (!this.supportsPagination) { if (!this.supportsPagination) {
rows = luceneLimit(rows, limit) rows = rows.slice(pageNumber * limit, (pageNumber + 1) * limit)
// rows = luceneLimit(rows, limit)
} }
return { return {
@ -207,17 +209,27 @@ export default class TableFetch {
} }
/** /**
* Gets the schema definition for a datasource. * Gets the definition for this datasource.
* Defaults to fetching a table definition. * Defaults to fetching a table definition.
* @param datasource the datasource definition * @param datasource
* @return {object} the schema * @return {object} the definition
*/ */
static async getSchema(datasource) { static async getDefinition(datasource) {
if (!datasource?.tableId) { if (!datasource?.tableId) {
return null return null
} }
const table = await fetchTableDefinition(datasource.tableId) return await fetchTableDefinition(datasource.tableId)
return this.enrichSchema(table?.schema) }
/**
* Gets the schema definition for a datasource.
* Defaults to getting the "schema" property of the definition.
* @param datasource the datasource
* @param definition the datasource definition
* @return {object} the schema
*/
static getSchema(datasource, definition) {
return definition?.schema
} }
/** /**
@ -225,7 +237,7 @@ export default class TableFetch {
* @param schema the datasource schema * @param schema the datasource schema
* @return {object} the enriched datasource schema * @return {object} the enriched datasource schema
*/ */
static enrichSchema(schema) { enrichSchema(schema) {
if (schema == null) { if (schema == null) {
return null return null
} }
@ -246,6 +258,17 @@ export default class TableFetch {
return enrichedSchema return enrichedSchema
} }
/**
* Determine the feature flag for this datasource definition
* @param definition
*/
// eslint-disable-next-line no-unused-vars
determineFeatureFlags(definition) {
this.supportsSearch = false
this.supportsSort = false
this.supportsPagination = false
}
/** /**
* Resets the data set and updates options * Resets the data set and updates options
* @param newOptions any new options * @param newOptions any new options
@ -319,6 +342,7 @@ export default class TableFetch {
...$store, ...$store,
loading: true, loading: true,
cursor: nextCursor, cursor: nextCursor,
pageNumber: $store.pageNumber + 1,
})) }))
const { rows, info, hasNextPage, cursor } = await this.getPage() const { rows, info, hasNextPage, cursor } = await this.getPage()
@ -326,11 +350,10 @@ export default class TableFetch {
this.store.update($store => { this.store.update($store => {
let { cursors, pageNumber } = $store let { cursors, pageNumber } = $store
if (hasNextPage) { if (hasNextPage) {
cursors[pageNumber + 2] = cursor cursors[pageNumber + 1] = cursor
} }
return { return {
...$store, ...$store,
pageNumber: pageNumber + 1,
rows, rows,
info, info,
cursors, cursors,
@ -354,6 +377,7 @@ export default class TableFetch {
...$store, ...$store,
loading: true, loading: true,
cursor: prevCursor, cursor: prevCursor,
pageNumber: $store.pageNumber - 1,
})) }))
const { rows, info } = await this.getPage() const { rows, info } = await this.getPage()
@ -361,7 +385,6 @@ export default class TableFetch {
this.store.update($store => { this.store.update($store => {
return { return {
...$store, ...$store,
pageNumber: $store.pageNumber - 1,
rows, rows,
info, info,
loading: false, loading: false,

View File

@ -1,18 +1,25 @@
import DataFetch from "./DataFetch.js" import DataFetch from "./DataFetch.js"
import { executeQuery, fetchQueryDefinition } from "api" import { executeQuery, fetchQueryDefinition } from "api"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { get } from "svelte/store"
export default class ViewFetch extends DataFetch { export default class QueryFetch extends DataFetch {
static async getSchema(datasource) { determineFeatureFlags(definition) {
console.log("pagination config", definition?.pagination)
this.supportsPagination =
definition?.fields?.pagination?.type != null &&
definition?.fields?.pagination?.pageParam != null
}
static async getDefinition(datasource) {
if (!datasource?._id) { if (!datasource?._id) {
return null return null
} }
const definition = await fetchQueryDefinition(datasource._id) return await fetchQueryDefinition(datasource._id)
return this.enrichSchema(definition?.schema)
} }
async getData() { async getData() {
const { datasource } = this.options const { datasource, limit } = this.options
// Set the default query params // Set the default query params
let parameters = cloneDeep(datasource?.queryParams || {}) let parameters = cloneDeep(datasource?.queryParams || {})
@ -22,13 +29,21 @@ export default class ViewFetch extends DataFetch {
} }
} }
const { data, ...rest } = await executeQuery({ // Add pagination to query if supported
queryId: datasource?._id, let queryPayload = { queryId: datasource?._id, parameters }
parameters, if (this.supportsPagination) {
}) const { cursor, definition, pageNumber } = get(this.store)
const { type } = definition.fields.pagination.type
const page = type === "page" ? pageNumber : cursor
queryPayload.pagination = { page, limit }
}
const { data, pagination, ...rest } = await executeQuery(queryPayload)
return { return {
rows: data || [], rows: data || [],
info: rest, info: rest,
cursor: pagination?.page,
hasNextPage: data?.length === limit && limit > 0,
} }
} }
} }

View File

@ -1,7 +1,7 @@
import DataFetch from "./DataFetch.js" import DataFetch from "./DataFetch.js"
import { fetchRelationshipData } from "api" import { fetchRelationshipData } from "api"
export default class ViewFetch extends DataFetch { export default class RelationshipFetch extends DataFetch {
async getData() { async getData() {
const { datasource } = this.options const { datasource } = this.options
const res = await fetchRelationshipData({ const res = await fetchRelationshipData({

View File

@ -3,12 +3,10 @@ import DataFetch from "./DataFetch.js"
import { searchTable } from "api" import { searchTable } from "api"
export default class TableFetch extends DataFetch { export default class TableFetch extends DataFetch {
constructor(opts) { determineFeatureFlags() {
super(opts, { this.supportsSearch = true
supportsSearch: true, this.supportsSort = true
supportsSort: true, this.supportsPagination = true
supportsPagination: true,
})
} }
async getData() { async getData() {

View File

@ -1,18 +1,17 @@
import DataFetch from "./DataFetch.js" import DataFetch from "./DataFetch.js"
import { fetchTableDefinition, fetchViewData } from "api" import { fetchViewData } from "api"
export default class ViewFetch extends DataFetch { export default class ViewFetch extends DataFetch {
static async getSchema(datasource) { static async getSchema(datasource, definition) {
if (!datasource?.tableId) { const schema = definition?.views?.[datasource.name]?.schema
return null console.log(schema)
} return schema
const table = await fetchTableDefinition(datasource.tableId)
return this.enrichSchema(table?.views?.[datasource.name]?.schema)
} }
async getData() { async getData() {
const { datasource } = this.options const { datasource } = this.options
const res = await fetchViewData(datasource) const res = await fetchViewData(datasource)
console.log(res)
return { return {
rows: res || [], rows: res || [],
} }

View File

@ -35,14 +35,15 @@ export const fetchDatasourceSchema = async datasource => {
// All normal datasource schema can use their corresponsing implementations // All normal datasource schema can use their corresponsing implementations
// in the data fetch classes // in the data fetch classes
if (type === "table" || type === "link") { const handler = {
return TableFetch.getSchema(datasource) table: TableFetch,
} link: TableFetch,
if (type === "view") { view: ViewFetch,
return ViewFetch.getSchema(datasource) query: QueryFetch,
} }[type]
if (type === "query") { if (handler) {
return QueryFetch.getSchema(datasource) const definition = await handler.getDefinition(datasource)
return handler.getSchema(datasource, definition)
} }
return null return null