1
0
mirror of https://github.com/garraflavatra/rolens.git synced 2025-01-18 21:17:59 +00:00

Aggregation

This commit is contained in:
Romein van Buren 2023-02-18 15:41:53 +01:00
parent 715519b06d
commit 1b2315b0b0
Signed by: romein
GPG Key ID: 0EFF8478ADDF6C49
14 changed files with 526 additions and 8 deletions

View File

@ -54,6 +54,10 @@
}
summary::before {
content: '';
display: none;
}
summary::-webkit-details-marker {
display: none;
}
summary :global(svg) {
width: 14px;

View File

@ -87,5 +87,9 @@
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12"/>
{:else if name === 'save'}
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8M7 3v5h8"/>
{:else if name === 're'}
<path d="m17 1 4 4-4 4"/><path d="M3 11V9a4 4 0 0 1 4-4h14M7 23l-4-4 4-4"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/>
{:else if name === 'chart'}
<path d="M18 20V10M12 20V4M6 20v-6"/>
{/if}
</svg>

View File

@ -0,0 +1,92 @@
<script>
import { locales } from '$lib/mongo';
const defaultCollation = {
locale: 'en_US',
strength: 3,
caseLevel: false,
caseFirst: 'off',
numericOrdering: false,
alternate: 'non-ignorable',
maxVariable: 'punct',
backwards: false,
normalization: false,
};
export let collation = { ...defaultCollation };
</script>
<div class="settinggrid">
<label for="collationLocale">Locale</label>
<div class="field">
<select id="collationLocale" bind:value={collation.locale}>
{#each Object.entries(locales) as [value, name]}
<option {value}>({value}) {name}</option>
{/each}
</select>
</div>
<label for="collationStrength">Strength</label>
<div class="field">
<select id="collationStrength" bind:value={collation.strength}>
<option value={1}>(1) Collate base characters only.</option>
<option value={2}>(2) Collate up to secondary differences, such as diacritics.</option>
<option value={3}>(3) Collate up to tertiary differences, such as case and letter variants.</option>
<option value={4}>(4) Limited for specific use case such as processing Japanese text.</option>
<option value={5}>(5) Identical Level. Limited for specific use case of tie breaker.</option>
</select>
</div>
<label for="collationCaseLevel">Use case level</label>
<div class="field">
<span class="checkbox">
<input type="checkbox" id="collationCaseLevel" bind:checked={collation.caseLevel} />
</span>
</div>
<label for="collationCaseFirst">Case first</label>
<div class="field">
<select id="collationCaseFirst" bind:value={collation.caseFirst}>
<option value="off">(off) Similar to "lower" with slight differences.</option>
<option value="upper">(upper) Uppercase sorts before lowercase.</option>
<option value="lower">(lower) Lowercase sorts before uppercase.</option>
</select>
</div>
<label for="collationNumericOrdering">Numeric ordering</label>
<div class="field">
<span class="checkbox">
<input type="checkbox" id="collationNumericOrdering" bind:checked={collation.numericOrdering} />
</span>
</div>
<label for="collationAlternate">Alternate</label>
<div class="field">
<select id="collationAlternate" bind:value={collation.alternate}>
<option value="non-ignorable">(non-ignorable) Whitespace and punctuation are considered base characters.</option>
<option value="shifted">(shifted) Whitespace and punctuation are considered base characters.</option>
</select>
</div>
<label for="collationMaxVariable">Max Variable</label>
<div class="field">
<select id="collationMaxVariable" bind:value={collation.maxVariable}>
<option value="punct">(punct) Both whitespace and punctuation are ignorable and not considered base characters.</option>
<option value="space">(space) Whitespace is ignorable and not considered to be base characters.</option>
</select>
</div>
<label for="collationBackwards">Backwards</label>
<div class="field">
<span class="checkbox">
<input type="checkbox" id="collationBackwards" bind:checked={collation.backwards} />
</span>
</div>
<label for="collationNormalization">Normalization</label>
<div class="field">
<span class="checkbox">
<input type="checkbox" id="collationNormalization" bind:checked={collation.normalization} />
</span>
</div>
</div>

View File

@ -18,7 +18,10 @@
<ul>
{#each tabs as tab (tab.key)}
<li class:active={tab.key === selectedKey}>
<button class="tab" on:click={() => select(tab.key)}>{tab.title}</button>
<button class="tab" on:click={() => select(tab.key)}>
{#if tab.icon} <Icon name={tab.icon} /> {/if}
{tab.title}
</button>
{#if tab.closable}
<button class="btn-sm" on:click={() => dispatch('closeTab', tab.key)}>
<Icon name="x" />

View File

@ -0,0 +1,41 @@
[
"$addFields",
"$bucket",
"$bucketAuto",
"$changeStream",
"$collStats",
"$count",
"$currentOp",
"$densify",
"$documents",
"$facet",
"$fill",
"$geoNear",
"$graphLookup",
"$group",
"$indexStats",
"$limit",
"$listLocalSessions",
"$listSessions",
"$lookup",
"$match",
"$merge",
"$out",
"$planCacheStats",
"$project",
"$redact",
"$replaceRoot",
"$replaceWith",
"$sample",
"$search",
"$searchMeta",
"$set",
"$setWindowFields",
"$shardedDataDistribution",
"$skip",
"$sort",
"$sortByCount",
"$unionWith",
"$unset",
"$unwind"
]

View File

@ -0,0 +1,152 @@
{
"af": "Afrikaans",
"sq": "Albanian",
"am": "Amharic",
"ar": "Arabic",
"ar@collation=compat": "Arabic",
"hy": "Armenian",
"as": "Assamese",
"az": "Azeri",
"az@collation=search": "Azeri",
"be": "Belarusian",
"bn": "Bengali",
"bn@collation=traditional": "Bengali",
"bs": "Bosnian",
"bs@collation=search": "Bosnian",
"bs_Cyrl": "Bosnian (Cyrillic)",
"bg": "Bulgarian",
"my": "Burmese",
"ca": "Catalan",
"ca@collation=search": "Catalan",
"chr": "Cherokee",
"zh": "Chinese",
"zh@collation=big5han": "Chinese",
"zh@collation=gb2312han": "Chinese",
"zh@collation=unihan": "Chinese",
"zh@collation=zhuyin": "Chinese",
"zh_Hant": "Chinese (Traditional)",
"hr": "Croatian",
"hr@collation=search": "Croatian",
"cs": "Czech",
"cs@collation=search": "Czech",
"da": "Danish",
"da@collation=search": "Danish",
"nl": "Dutch",
"dz": "Dzongkha",
"en": "English",
"en_US": "English (United States)",
"en_US_POSIX": "English (United States, Computer)",
"eo": "Esperanto",
"et": "Estonian",
"ee": "Ewe",
"fo": "Faroese",
"fil": "Filipino",
"fi": "Finnish",
"fi@collation=search": "Finnish",
"fi@collation=traditional": "Finnish",
"fr": "French",
"fr_CA": "French (Canada)",
"gl": "Galician",
"gl@collation=search": "Galician",
"ka": "Georgian",
"de": "German",
"de@collation=eor": "German",
"de@collation=phonebook": "German",
"de@collation=search": "German",
"de_AT": "German (Austria)",
"de_AT@collation=phonebook": "German (Austria)",
"el": "Greek",
"gu": "Gujarati",
"ha": "Hausa",
"haw": "Hawaiian",
"he": "Hebrew",
"he@collation=search": "Hebrew",
"hi": "Hindi",
"hu": "Hungarian",
"is": "Icelandic",
"is@collation=search": "Icelandic",
"ig": "Igbo",
"smn": "Inari Sami",
"smn@collation=search": "Inari Sami",
"id": "Indonesian",
"ga": "Irish",
"it": "Italian",
"ja": "Japanese",
"ja@collation=unihan": "Japanese",
"kl": "Kalaallisut",
"kl@collation=search": "Kalaallisut",
"kn": "Kannada",
"kn@collation=traditional": "Kannada",
"kk": "Kazakh",
"km": "Khmer",
"kok": "Konkani",
"ko": "Korean",
"ko@collation=search": "Korean",
"ko@collation=searchjl": "Korean",
"ko@collation=unihan": "Korean",
"ky": "Kyrgyz",
"lkt": "Lakota",
"lo": "Lao",
"lv": "Latvian",
"ln": "Lingala",
"ln@collation=phonetic": "Lingala",
"lt": "Lithuanian",
"dsb": "Lower Sorbian",
"lb": "Luxembourgish",
"mk": "Macedonian",
"ms": "Malay",
"ml": "Malayalam",
"mt": "Maltese",
"mr": "Marathi",
"mn": "Mongolian",
"ne": "Nepali",
"se": "Northern Sami",
"se@collation=search": "Northern Sami",
"nb": "Norwegian Bokmål",
"nb@collation=search": "Norwegian Bokmål",
"nn": "Norwegian Nynorsk",
"nn@collation=search": "Norwegian Nynorsk",
"or": "Oriya",
"om": "Oromo",
"ps": "Pashto",
"fa": "Persian",
"fa_AF": "Persian (Afghanistan)",
"pl": "Polish",
"pt": "Portuguese",
"pa": "Punjabi",
"ro": "Romanian",
"ru": "Russian",
"sr": "Serbian",
"sr_Latn": "Serbian (Latin)",
"sr_Latn@collation=search": "Serbian (Latin)",
"si": "Sinhala",
"si@collation=dictionary": "Sinhala",
"sk": "Slovak",
"sk@collation=search": "Slovak",
"sl": "Slovenian",
"es": "Spanish",
"es@collation=traditional": "Spanish",
"es@collation=search": "Spanish",
"sw": "Swahili",
"sv": "Swedish",
"sv@collation=search": "Swedish",
"ta": "Tamil",
"te": "Telugu",
"th": "Thai",
"bo": "Tibetan",
"to": "Tongan",
"tr": "Turkish",
"tr@collation=search": "Turkish",
"uk": "Ukrainian",
"hsb": "Upper Sorbian",
"ur": "Urdu",
"ug": "Uyghur",
"vi": "Vietnamese",
"vi@collation=traditional": "Vietnamese",
"wae": "Walser",
"cy": "Welsh",
"yi": "Yiddish",
"yi@collation=search": "Yiddish",
"yo": "Yoruba",
"zu": "Zulu"
}

View File

@ -1,4 +1,8 @@
import { ObjectId } from 'bson';
import aggregationStages from './mongo-aggregation-stages.json';
import locales from './mongo-locales.json';
export { aggregationStages, locales };
// Calculate the min and max values of (un)signed integers with n bits
export const intMin = bits => Math.pow(2, bits - 1) * -1;
@ -32,3 +36,8 @@ export function canBeObjectId(value) {
return false;
}
}
export function aggregationStageDocumentationURL(stageName) {
const url = `https://www.mongodb.com/docs/manual/reference/operator/aggregation/${stageName.replace('$', '')}/`;
return url;
}

View File

@ -0,0 +1,127 @@
<script>
import Details from '$components/details.svelte';
import Icon from '$components/icon.svelte';
import Modal from '$components/modal.svelte';
import MongoCollation from '$components/mongo-collation.svelte';
import input from '$lib/actions/input';
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo';
import { Aggregate } from '$wails/go/app/App';
import { BrowserOpenURL } from '$wails/runtime/runtime';
import { onMount } from 'svelte';
export let collection;
const options = {};
let stages = [];
let settingsModalOpen = false;
$: invalid = !stages.length || stages.some(stage => {
try {
JSON.parse(stage.data);
return false;
}
catch {
return true;
}
});
function addStage() {
stages = [ ...stages, { type: '$match' } ];
}
function deleteStage(index) {
stages = [ ...stages.slice(0, index), ...stages.slice(index + 1) ];
}
function openStageDocs(type) {
const url = aggregationStageDocumentationURL(type);
BrowserOpenURL(url);
}
async function run() {
const pipeline = stages.map(stage => ({ [stage.type]: JSON.parse(stage.data) }));
await Aggregate(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(pipeline), JSON.stringify(options));
}
onMount(() => {
if (!stages.length) {
stages = [ { type: '$match' } ];
}
});
</script>
<form class="aggregate" on:submit|preventDefault={run}>
<div>
{#each stages as stage, index}
<Details title="#{index + 1}: {stage.type}" deletable={true} on:delete={() => deleteStage(index)} initiallyOpen>
<label class="field">
<span class="label">Stage type</span>
<select bind:value={stage.type}>
{#each aggregationStages as value}
<option {value}>{value}</option>
{/each}
</select>
<button class="btn secondary" type="button" on:click={() => openStageDocs(stage.type)} title="Open documentation about {stage.type || 'this stage'} on mongodb.org">
<Icon name="info" />
</button>
</label>
<label class="field">
<textarea bind:value={stage.data} class="code" use:input={{ type: 'json' }}></textarea>
</label>
</Details>
{/each}
<button class="btn-sm" type="button" on:click={addStage}>
<Icon name="+" /> Add stage
</button>
</div>
<div class="controls">
<div>
<button class="btn" type="submit" disabled={invalid}>
<Icon name="play" /> Run pipeline
</button>
<button class="btn" type="button" on:click={() => settingsModalOpen = true}>
<Icon name="cog" /> Settings
</button>
</div>
</div>
</form>
<Modal title="Advanced settings" bind:show={settingsModalOpen}>
<div class="settinggrid">
<label for="allowDiskUse">Allow disk use</label>
<div class="field">
<span class="checkbox">
<input type="checkbox" id="allowDiskUse" bind:checked={options.allowDiskUse} />
</span>
</div>
</div>
<Details title="Set custom collation options">
<MongoCollation bind:collation={options.collation} />
</Details>
</Modal>
<style>
.aggregate {
display: grid;
gap: 0.5rem;
grid-template: 1fr auto / 1fr;
}
.settinggrid {
margin-bottom: 0.5rem;
}
.controls {
display: flex;
justify-content: space-between;
align-items: center;
}
textarea {
min-height: 100px;
margin-top: 0.5rem;
}
</style>

View File

@ -3,6 +3,7 @@
import TabBar from '$components/tabbar.svelte';
import { EventsOn } from '$wails/runtime/runtime';
import { tick } from 'svelte';
import Aggregate from './aggregate.svelte';
import ViewConfig from './components/viewconfig.svelte';
import Find from './find.svelte';
import Indexes from './indexes.svelte';
@ -46,12 +47,13 @@
{#if collection}
{#key collection}
<TabBar tabs={[
{ key: 'stats', title: 'Stats' },
{ key: 'find', title: 'Find' },
{ key: 'insert', title: 'Insert' },
{ key: 'update', title: 'Update' },
{ key: 'remove', title: 'Remove' },
{ key: 'indexes', title: 'Indexes' },
{ key: 'stats', icon: 'chart', title: 'Stats' },
{ key: 'find', icon: 'db', title: 'Find' },
{ key: 'insert', icon: '+', title: 'Insert' },
{ key: 'update', icon: 'edit', title: 'Update' },
{ key: 'remove', icon: 'trash', title: 'Remove' },
{ key: 'indexes', icon: 'list', title: 'Indexes' },
{ key: 'aggregate', icon: 're', title: 'Aggregate' },
]} bind:selectedKey={tab} />
<div class="container">
@ -61,6 +63,7 @@
{:else if tab === 'update'} <Update {collection} on:performFind={catchQuery} />
{:else if tab === 'remove'} <Remove {collection} />
{:else if tab === 'indexes'} <Indexes {collection} />
{:else if tab === 'aggregate'} <Aggregate {collection} />
{/if}
</div>
{/key}

View File

@ -179,6 +179,13 @@ code,
font-family: Menlo, monospace;
}
.settinggrid {
display: grid;
grid-template-columns: 1fr 3fr;
align-items: center;
gap: 0.5rem;
}
@keyframes flashGreen {
0% {
color: #0d0;

View File

@ -7,6 +7,8 @@ import {menu} from '../models';
export function AddHost(arg1:string):Promise<void>;
export function Aggregate(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string):Promise<void>;
export function CreateIndex(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
export function DropCollection(arg1:string,arg2:string,arg3:string):Promise<boolean>;

View File

@ -6,6 +6,10 @@ export function AddHost(arg1) {
return window['go']['app']['App']['AddHost'](arg1);
}
export function Aggregate(arg1, arg2, arg3, arg4, arg5) {
return window['go']['app']['App']['Aggregate'](arg1, arg2, arg3, arg4, arg5);
}
export function CreateIndex(arg1, arg2, arg3, arg4) {
return window['go']['app']['App']['CreateIndex'](arg1, arg2, arg3, arg4);
}

View File

@ -0,0 +1,70 @@
package app
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func (a *App) Aggregate(hostKey, dbKey, collKey, pipelineJson, settingsJson string) {
var settings *options.AggregateOptions
if err := json.Unmarshal([]byte(settingsJson), &settings); err != nil {
runtime.LogError(a.ctx, "Could not parse aggregation settings:")
runtime.LogError(a.ctx, err.Error())
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Type: runtime.ErrorDialog,
Title: "Couldn't parse aggregation settings",
Message: err.Error(),
})
return
}
var pipeline mongo.Pipeline
if err := bson.UnmarshalExtJSON([]byte(pipelineJson), true, &pipeline); err != nil {
runtime.LogWarning(a.ctx, "Could not parse aggregation pipeline:")
runtime.LogWarning(a.ctx, err.Error())
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Type: runtime.ErrorDialog,
Title: "Couldn't parse aggregation pipeline",
Message: err.Error(),
})
return
}
client, ctx, close, err := a.connectToHost(hostKey)
if err != nil {
return
}
defer close()
cursor, err := client.Database(dbKey).Collection(collKey).Aggregate(ctx, pipeline, settings)
if err != nil {
runtime.LogWarning(a.ctx, "Could not get aggregation cursor:")
runtime.LogWarning(a.ctx, err.Error())
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Type: runtime.ErrorDialog,
Title: "Couldn't get aggregation cursor",
Message: err.Error(),
})
return
}
var results []bson.M
if err := cursor.All(ctx, &results); err != nil {
runtime.LogInfo(a.ctx, "Error while running aggregation pipeline:")
runtime.LogInfo(a.ctx, err.Error())
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
Type: runtime.ErrorDialog,
Title: "Error while running aggregation pipeline",
Message: err.Error(),
})
return
}
fmt.Println(results)
}

View File

@ -21,7 +21,7 @@ type QueryResult struct {
Results []string `json:"results"`
}
func (a *App) FindItems(hostKey, dbKey, collKey string, formJson string) QueryResult {
func (a *App) FindItems(hostKey, dbKey, collKey, formJson string) QueryResult {
var out QueryResult
var form Query