Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Personal preference changes, sort, pick, omit, double click to filter etc #1

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
155 changes: 142 additions & 13 deletions lib/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ const { purgeTable } = require('./actions/purgeTable')
const asyncMiddleware = require('./utils/asyncMiddleware')
const bodyParser = require('body-parser')
const pickBy = require('lodash.pickby')
const omit = require('lodash.omit')
const clc = require('cli-color')
const cookieParser = require('cookie-parser')
const DEFAULT_THEME = process.env.DEFAULT_THEME || 'light'
const PAGE_SIZE = 100

function loadDynamoEndpoint(env, dynamoConfig) {
if (typeof env.DYNAMO_ENDPOINT === 'string') {
Expand Down Expand Up @@ -386,7 +388,7 @@ exports.createServer = (dynamodb, docClient) => {
return pageItems.length > pageSize || !lastStartKey
}

return doSearch(docClient, TableName, scanParams, 10, startKey, onNewItems, operationType)
return doSearch(docClient, TableName, scanParams, pageSize, startKey, onNewItems, operationType)
.then(items => {
let nextKey = null

Expand Down Expand Up @@ -420,7 +422,8 @@ exports.createServer = (dynamodb, docClient) => {
'<=': '<=',
'>': '>',
'<': '<',
'begins_with': 'begins_with'
'begins_with': 'begins_with',
'contains': 'contains'
},
attributeTypes: {
'S': 'String',
Expand Down Expand Up @@ -486,8 +489,14 @@ exports.createServer = (dynamodb, docClient) => {

if (filters[key].operator === 'begins_with') {
FilterExpressions.push(`${filters[key].operator} ( #key${i} , :key${i})`)
} else if (filters[key].operator === 'contains') {
FilterExpressions.push(
`${filters[key].operator} ( #key${i} , :key${i})`
)
} else {
FilterExpressions.push(`#key${i} ${filters[key].operator} :key${i}`)
FilterExpressions.push(
`#key${i} ${filters[key].operator} :key${i}`
)
}
}
// Increment the unique ID variable
Expand Down Expand Up @@ -521,43 +530,163 @@ exports.createServer = (dynamodb, docClient) => {
? ExclusiveStartKey
: undefined

return getPage(docClient, description.Table.KeySchema, TableName,
params, 25, startKey, req.query.operationType)
.then(results => {
return getPage(
docClient,
description.Table.KeySchema,
TableName,
params,
PAGE_SIZE,
startKey,
req.query.operationType
)
.then((results) => {
const { pageItems, nextKey } = results

const primaryKeys = description.Table.KeySchema.map(
(schema) => schema.AttributeName
)
const omittedProps = (req.query.omit || '')
.split(',')
.map((x) => x.trim())
.filter((x) => x)
const pickedProps = (req.query.pick || '')
.split(',')
.map((x) => x.trim())
.filter((x) => x)
const sortBy = req.query.sort
const isDescending = (req.query.direction || 'asc') === 'desc'

const allKeys = extractKeysForItems(pageItems)

if (req.query.hidegsi) {
const gsiKeys = allKeys.filter(k => /^GSI\d[PS]K$/.test(k))
omittedProps.push(...gsiKeys)
}

let filteredItems = pageItems.map((item) =>
omit(item, omittedProps)
)

// for normal dynamo with type, use that, if not use onetable type
const hasType = allKeys.includes('type')
const typeKey = hasType ? 'type' : '_type'

if (pickedProps.length) {
//always add type
pickedProps.push(typeKey)
// ensure startedAt/endedAt pair, even if missing data
if (pickedProps.includes('startedAt') && !pickedProps.includes('endedAt')) {
pickedProps.push('endedAt')
}

// now the reverse
if (pickedProps.includes('endedAt') && !pickedProps.includes('startedAt')) {
pickedProps.push('startedAt')
}

filteredItems = filteredItems.map((item) =>
pickBy(
item,
(val, key) =>
pickedProps.includes(key) || primaryKeys.includes(key)
)
)
}

if (sortBy) {
filteredItems.sort((a, b) => {
const aVal = isDescending ? b[sortBy] : a[sortBy]
const bVal = isDescending ? a[sortBy] : b[sortBy]
if (aVal === undefined && bVal === undefined) {
return 0
}
if (aVal === undefined) {
return 1
}
if (bVal === undefined) {
return -1
}
const compare = String(aVal || '').localeCompare(
String(bVal || '')
)
return compare
})
}

const nextKeyParam = nextKey
? encodeURIComponent(JSON.stringify(nextKey))
: null

const primaryKeys = description.Table.KeySchema.map(
schema => schema.AttributeName)
// add special type key always
const sortedFirstKeys = [typeKey]
const restOfAllKeys = extractKeysForItems(filteredItems)
.filter(
(key) =>
!primaryKeys.includes(key) && !sortedFirstKeys.includes(key)
)
.sort((a, b) => a.localeCompare(b))

const dateKeys = restOfAllKeys.filter((key) => /At$/i.test(key))
const restOfKeys = restOfAllKeys.filter((key) => !/At$/i.test(key))


// ensure startedAt/endedAt pair, even if missing data
if (dateKeys.includes('startedAt') && !dateKeys.includes('endedAt')) {
dateKeys.push('endedAt')
}

// now the reverse
if (dateKeys.includes('endedAt') && !dateKeys.includes('startedAt')) {
dateKeys.push('startedAt')
}

// in reverse order
const specialOrderKeys = [
'startedAt',
'endedAt',
'createdAt',
'updatedAt',
].reverse()
dateKeys.sort(
(a, b) =>
specialOrderKeys.indexOf(b) - specialOrderKeys.indexOf(a)
)

// Primary keys are listed first.
const uniqueKeys = [
...primaryKeys,
...extractKeysForItems(pageItems).filter(key => !primaryKeys.includes(key)),
...sortedFirstKeys,
...restOfKeys,
...dateKeys,
]

// Append the item key.
for (const item of pageItems) {
for (const item of filteredItems) {
item.__key = extractKey(item, description.Table.KeySchema)
}

const data = Object.assign({}, description, {
query: req.query,
pageNum,
pageSize: PAGE_SIZE,
prevKey: encodeURIComponent(req.query.prevKey || ''),
startKey: encodeURIComponent(req.query.startKey || ''),
nextKey: nextKeyParam,
filterQueryString: encodeURIComponent(req.query.filters || ''),
Items: pageItems,
Items: filteredItems,
uniqueKeys,
pickedProps,
omittedProps,
})

res.json(data)
})
.catch(error => {
res.status(400).send((error.code ? '[' + error.code + '] ' : '') + error.message)
.catch((error) => {
res
.status(400)
.send(
(error.code ? '[' + error.code + '] ' : '') + error.message
)
})
})
}))
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"bin": "./bin/dynamodb-admin.js",
"scripts": {
"dev": "nodemon bin/dynamodb-admin.js",
"start": "node bin/dynamodb-admin.js",
"start": "DYNAMO_ENDPOINT=http://localhost:3413 node bin/dynamodb-admin.js",
"start:bapi:docker": "DYNAMO_ENDPOINT=http://localhost:3414 node bin/dynamodb-admin.js",
"start:oapi": "DYNAMO_ENDPOINT=http://localhost:5002 node bin/dynamodb-admin.js",
"lint": "eslint --ext .js . && ejslint views",
"fix": "eslint --ext .js --fix .",
"test": "jest",
Expand Down Expand Up @@ -40,6 +42,7 @@
"ejs": "^3.1.6",
"errorhandler": "^1.5.1",
"express": "^4.17.1",
"lodash.omit": "^4.5.0",
"lodash.pickby": "^4.6.0",
"open": "^8.2.1"
},
Expand Down
Loading