More re-org.

Try to move message searching implementation into separate .js file.
Have apiFetch() put errors into state, as appropriate.
Add errorDetails display.
This commit is contained in:
Sage Vaillancourt 2022-08-26 18:46:21 -04:00
parent 8719371bd2
commit bc1ad5e45a
6 changed files with 163 additions and 93 deletions

View File

@ -18,10 +18,11 @@
Allow JavaScript to mutate objects
<input type=checkbox bind:checked={mutableObjects}>
</div>
<textarea class={"query-input" + (big ? ' big' : '')} bind:value={queryCode}></textarea>
<textarea class={'query-input' + (big ? ' big' : '')} bind:value={queryCode}></textarea>
</div>
{/if}
<!--suppress CssUnusedSymbol -->
<style>
.query-input-header {
display: flex;

View File

@ -41,10 +41,12 @@ export const state = writable({
itemCount: undefined,
matchCount: undefined,
error: 'Connecting to WebSocket...',
errorDetails: undefined,
})
const updateClearError = updater => state.update(s => {
s.error = null
s.errorDetails = null
return updater(s)
})
@ -129,6 +131,9 @@ export const query = async ({ cluster, topic, mode, jsFilter, queryCode, maxItem
}
export const connect = () => {
if (ws?.readyState === WebSocket.OPEN) {
return;
}
if (testMode) {
updateClearError(s => ({ ...s, error: 'TEST MODE ENABLED' }))
return

View File

@ -1 +1,65 @@
export const prerender = true;
import { apiFetch } from "../utils.js";
import { killQuery, query } from "../lib/state.js";
export const getTopics = async cluster =>
(await apiFetch(`/topics/${cluster}`) || []).sort()
export const mapFromLocalStorage = ({ checked, querySettings, jsonDisplay, clusterNames, topics }) => {
if (localStorage.querySettings) {
let storedSettings
try {
storedSettings = JSON.parse(localStorage.querySettings)
} catch (e) {
storedSettings = {}
}
querySettings.maxItems = storedSettings?.maxItems ?? querySettings.maxItems
querySettings.jsFilter = storedSettings?.jsFilter ?? querySettings.jsFilter
querySettings.queryCode = storedSettings?.queryCode ?? querySettings.queryCode
querySettings.mode = storedSettings?.mode ?? querySettings.mode
if (!checked || clusterNames.includes(storedSettings?.cluster)) {
querySettings.cluster = storedSettings.cluster
} else {
querySettings.cluster = clusterNames[0]
}
if (!checked || topics.includes(storedSettings?.topic)) {
querySettings.topic = storedSettings.topic
} else {
querySettings.topic = topics[0]
}
}
if (localStorage.jsonDisplay) {
try {
const savedJsonDisplay = JSON.parse(localStorage.jsonDisplay)
Object.entries(savedJsonDisplay).forEach(([key, value]) => {
if (savedJsonDisplay[key] !== undefined) {
jsonDisplay[key] = value
}
})
} catch (e) {
}
}
}
export const getClusterNames = async () => Object.keys((await apiFetch('/clusters')) || {})
export const startQuery = querySettings => {
localStorage.setItem('querySettings', JSON.stringify(querySettings))
query(querySettings)
return true
}
export const stopQuery = querySettings => {
killQuery(querySettings)
return false
}
export const defaultQueryCode =
`// \`message\` contains all metadata
// \`value\` is shorthand for message.value
// it contains the actual produced data
// \`json\` is a JSON representation of \`message\`
return message.value.eventType === "Television";`

View File

@ -2,92 +2,86 @@
import { onMount } from 'svelte'
import { JsonView } from '@zerodevx/svelte-json-view'
import QueryInput from '$lib/QueryInput.svelte'
import { state, connect, query, killQuery } from '$lib/state'
import { queryMode } from "../lib/constants.js"
import { apiFetch } from "../utils.js"
import Modal from "../lib/Modal.svelte";
import { state, connect } from '$lib/state'
import { queryMode } from '../lib/constants.js'
import Modal from '../lib/Modal.svelte';
import {
defaultQueryCode,
getClusterNames,
getTopics,
mapFromLocalStorage,
startQuery,
stopQuery
} from './+page.js';
let showQueryModal = false
let queryRunning = false
let expandAll = true
let showMetadata = true
const fontSizes = [30, 50, 67, 80, 90, 100, 110, 120, 133, 150]
let jsonDisplay = {
expandAll: true,
showMetadata: true,
fontSize: fontSizes.indexOf(100),
fontUp: () => setFontSize(Math.min(jsonDisplay.fontSize + 1, fontSizes.length - 1)),
fontDown: () => setFontSize(Math.max(jsonDisplay.fontSize - 1, 0))
}
const setFontSize = size => {
jsonDisplay.fontSize = size
localStorage.jsonDisplay = JSON.stringify(jsonDisplay)
}
const querySettings = {
let querySettings = {
topic: null,
cluster: null,
mode: queryMode.REAL_TIME,
jsFilter: false,
maxItems: 20,
mutableObjects: false,
queryCode:
`// \`message\` contains all metadata
// \`value\` is shorthand for message.value
// it contains the actual produced data
// \`json\` is a JSON representation of \`message\`
return message.value.eventType === "Television";`
queryCode: defaultQueryCode
}
let topics = []
let clusters = []
const updateTopics = async () => {
topics = await apiFetch(`/topics/${querySettings.cluster}`)
topics ??= []
topics.sort()
console.log('topics', topics)
querySettings.topic = topics[0]
let clusterNames = []
const updateTopics = async (updateTopic = true) => {
topics = await getTopics(querySettings.cluster)
if (updateTopic) {
querySettings.topic = topics[0]
}
}
const fontSizes = [30, 50, 67, 80, 90, 100, 110, 120, 133, 150]
let dataViewFontSize = 5
const setFontSize = size => {
dataViewFontSize = size
localStorage.dataViewFontSize = size
}
const fontUp = () => setFontSize(Math.min(dataViewFontSize + 1, fontSizes.length - 1))
const fontDown = () => setFontSize(Math.max(dataViewFontSize - 1, 0))
onMount(async () => {
connect()
clusters = Object.keys((await apiFetch('/clusters')) || {})
querySettings.cluster = clusters[0]
await updateTopics()
if (localStorage) {
dataViewFontSize = localStorage.dataViewFontSize || dataViewFontSize
if (localStorage.querySettings) {
let storedSettings = {}
try {
storedSettings = JSON.parse(localStorage.querySettings)
} catch (e) {}
querySettings.maxItems = storedSettings?.maxItems || querySettings.maxItems
querySettings.jsFilter = storedSettings?.jsFilter || querySettings.jsFilter
querySettings.queryCode = storedSettings?.queryCode || querySettings.queryCode
querySettings.mode = storedSettings?.mode || querySettings.mode
if (clusters.includes(storedSettings?.cluster)) {
querySettings.cluster = storedSettings.cluster
}
if (topics.includes(storedSettings?.topic)) {
querySettings.topic = storedSettings.topic
}
const loadSave = ({ checked }) => {
if (localStorage) {
mapFromLocalStorage({
checked,
jsonDisplay,
querySettings,
clusterNames,
topics,
})
jsonDisplay = jsonDisplay
querySettings = querySettings
}
}
})
let queryRunning = false
const startQuery = () => {
queryRunning = true
localStorage.setItem('querySettings', JSON.stringify(querySettings))
query(querySettings)
}
const stopQuery = () => {
queryRunning = false
killQuery(querySettings)
}
// Instantly load saved data as if cluster and topic names are accurate
// This should usually be the case
loadSave({ checked: false })
connect()
clusterNames = await getClusterNames()
await updateTopics()
// Reload with confirmed truthful values
// Will only flicker if topic or cluster names have changed
loadSave({ checked: true })
})
</script>
<svelte:head>
<title>{querySettings.topic ? `${querySettings.topic} - ` : ''}Kafka Dance</title>
<meta name="description" content="Kafka Dance" />
<meta name="description" content="Kafka Dance message search" />
</svelte:head>
<section>
@ -96,16 +90,19 @@ return message.value.eventType === "Television";`
<Header />
<NavBar />
-->
<h1>Message Search</h1>
<h1>Topic Search</h1>
{#if $state.error}
<div class="state-error">{$state.error}</div>
{#if $state.errorDetails}
<div class="state-error-details">{$state.errorDetails}</div>
{/if}
{/if}
<div class="settings-option">
<h3>Cluster</h3>
<select disabled={clusters.length === 0} bind:value={querySettings.cluster} name="cluster" id="cluster" on:change={updateTopics}>
{#each clusters as cluster}
<option value={cluster}>{cluster}</option>
<select disabled={clusterNames.length === 0} bind:value={querySettings.cluster} name="cluster" id="cluster" on:change={updateTopics}>
{#each clusterNames as clusterName}
<option value={clusterName}>{clusterName}</option>
{/each}
</select>
</div>
@ -167,28 +164,23 @@ return message.value.eventType === "Television";`
<div class="query-button">
{#if queryRunning}
<button class="danger" on:click={() => stopQuery()}>Stop Query</button>
<button class="danger" on:click={() => queryRunning = stopQuery(querySettings)}>Stop Query</button>
{:else}
<button class="colored" on:click={() => startQuery()}>Start Query</button>
<button class="colored" on:click={() => queryRunning = startQuery(querySettings)}>Start Query</button>
{/if}
</div>
<br>
<div class="live-settings">
<!--{#if $state.itemCount >= 0}
<h2>{$state.itemCount} Matches Found...</h2>
{/if}-->
</div>
</div>
<div class="data-view-container">
<div class="data-view-bar">
<div class="border-between">
<button class={expandAll ? 'selected' : ''} on:click={() => expandAll = !expandAll}>
Expand All Objects
<button class={jsonDisplay.expandAll ? 'selected' : ''}
on:click={() => jsonDisplay.expandAll = !jsonDisplay.expandAll}>
Expand All Objects
</button>
<button class={showMetadata ? 'selected' : ''} on:click={() => showMetadata = !showMetadata}>
Show Metadata
<button class={jsonDisplay.showMetadata ? 'selected' : ''}
on:click={() => jsonDisplay.showMetadata = !jsonDisplay.showMetadata}>
Show Metadata
</button>
</div>
<div style="display: flex; align-items: center">
@ -198,15 +190,15 @@ return message.value.eventType === "Television";`
</div>
{/if}
<div class="border-between zoom-buttons">
<button on:click={fontDown} style="border-color: #aaa;">-</button>
<button on:click={fontUp}>+</button>
<button on:click={jsonDisplay.fontDown} style="border-color: #aaa;">-</button>
<button on:click={jsonDisplay.fontUp}>+</button>
</div>
</div>
</div>
{#if $state.items?.length > 0}
<div class="data-view" style={`font-size: ${fontSizes[dataViewFontSize]}%`}>
<JsonView json={showMetadata ? $state.items : $state.items.map(item => item.value)}
depth={expandAll ? Infinity : 1}/>
<div class="data-view" style={`font-size: ${fontSizes[jsonDisplay.fontSize]}%`}>
<JsonView json={jsonDisplay.showMetadata ? $state.items : $state.items.map(item => item.value)}
depth={jsonDisplay.expandAll ? Infinity : 1}/>
</div>
{:else }
<div class="data-view no-query-data">
@ -322,6 +314,11 @@ return message.value.eventType === "Television";`
color: #ff2222;
font-weight: bold;
}
.state-error-details {
color: #ff2222;
font-weight: normal;
font-size: 80%;
}
/* JsonView config
:root {

View File

@ -41,8 +41,7 @@
clusterName: '', // and will be POSTed as a new cluster, not PUTted, updating an existing one
clientId: '',
brokers: [''],
useSasl: true,
ssl: true,
ssl: false,
sasl: {
mechanism: '',
username: '',
@ -69,7 +68,7 @@
</svelte:head>
<section>
<div class="query-settings">
<form class="query-settings" method="post">
<h1>Cluster Configuration</h1>
<div class="settings-option">
@ -109,7 +108,7 @@
<!-- TODO: Add options for SSL config -->
<div class="settings-option">
<div>SASL: <input type=checkbox bind:checked={useSasl} /></div>
<div>Use SASL: <input type=checkbox bind:checked={useSasl} /></div>
{#if useSasl}
<div class="settings-sub-option">
<div>Auth Mechanism:</div>
@ -132,7 +131,7 @@
<button style="float: right;"
on:click={() => (config.originalName ? updateCluster() : addCluster())}>{config.originalName ? 'Update Cluster Config' : 'Add Cluster Config'}</button>
</div>
</form>
<div class="data-view">
<button style="margin: 1em;" on:click={startNewCluster}>Add New Cluster</button>

View File

@ -65,7 +65,11 @@ export const apiFetch = async (path, options = undefined) => {
return undefined
}
try {
return response.json()
const json = await response.json()
if (json.error) {
state.update(s => ({ ...s, error: json.error, errorDetails: json.errorDetails }))
}
return json
} catch (e) {
console.error(e)
state.update(s => ({ ...s, error: 'Received non-JSON response from backend' }))