add internal simulation flag and diff

This commit is contained in:
david 2026-09-19 16:15:50 +03:00
parent 32fb1c87ac
commit cdc6cd2204
66 changed files with 3746 additions and 2509 deletions

View File

@ -1503,9 +1503,12 @@ describe("pmBlockToY", () => {
},
})
// the simulation flag is the one metric attribute whose schema
// default is not null, so it is written out with the block
expect(attrsOf(attached(el))).toEqual({
uid: "metric",
queries: [{ query: "up", legend: "uptime" }],
simulationActive: false,
})
})

View File

@ -246,6 +246,28 @@ describe("editor schema", () => {
)
})
it("keeps a metric block's simulation flag", ({ expect }) => {
const block = metricBlock("m1")
const result = roundTrip({
...block,
attrs: {
...block.attrs,
simulationActive: true,
},
})
expect(result.attrs?.simulationActive).toBe(true)
})
it("reads a metric block without a simulation flag as not simulating", ({
expect,
}) => {
const result = roundTrip(metricBlock("m1"))
expect(result.attrs?.simulationActive).toBe(false)
})
it("keeps a mermaid block's source text", ({ expect }) => {
const result = roundTrip({
type: "mermaidBlock",

View File

@ -45,6 +45,7 @@ export const MetricBlock = Node.create({
axisBoundsMin: {},
axisBoundsMax: {},
simulationPreset: {},
simulationActive: { default: false },
width: {},
}
},

View File

@ -42,7 +42,7 @@ for free.
handler; `decodeHookSettings` switches on type into the processor's own
struct.
- **A block write reports from the operation, never a re-read** (the
realtime service persists on a debounce): `expandForWrite` once, ship the
realtime service persists on a debounce): `sanitizeBlock` once, ship the
canonical uids, return `blockRows` of the expanded tree.
- **Branches are addressed by id, always.** Every content tool requires
`branch_id`; listings carry the ids; `FetchDocumentByBranchID` refuses a
@ -65,7 +65,8 @@ for free.
`DescribeInput` is its read-only half.
- **A write owns its invariants** on the `Input` method that performs it
(`MoveDocument` refuses a missing parent or a move under its own subtree),
not in `Execute`.
not in `Execute`. Block writes go through `ApplyEdit`, so their per-type
rules sit in `sanitizeBlock` and `sanitizeBlockAttrs` (`block.go`).
- **`Traits()` says what a tool is**; the mixins
`plainSummary`/`plainTraits`/`plainTitle` cover the defaults. `Write`
gates behind confirmation and protects the result from context

View File

@ -367,12 +367,14 @@ func compactFigma(b document.Block, uid string) Block {
}
}
// compactMetric preserves every non-uid attribute on the source
// metric block — the configuration shape is opaque to the canonical
// layer and the AI sees it through-pass.
// compactMetric keeps every attribute of the source metric block except
// its uid and its simulation flag. The configuration shape is opaque to
// the canonical layer, so it passes through as is. The flag is internal
// state that a reader never sees.
func compactMetric(b document.Block, uid string) Block {
attrs := b.Attrs.Copy()
delete(attrs, document.AttrUID)
delete(attrs, document.AttrSimulationActive)
return Block{
Type: BlockMetric,

View File

@ -394,12 +394,13 @@ func Test_Compact(t *testing.T) {
Attrs: map[string]any{"query": "up", "unit": "%"},
},
},
"Metric keeps an active simulation": {
"Metric keeps its preset and hides its simulation flag": {
Input: document.Block{
Type: document.BlockNodeMetricBlock,
Attrs: map[string]any{
"uid": "m1",
"simulationPreset": "http_latency",
"simulationActive": true,
},
},
Expected: Block{

View File

@ -358,11 +358,18 @@ func expandFigma(b Block) document.Block {
// expandMetric builds a metricBlock. All caller-supplied attrs are
// passed through unchanged (the metric configuration shape is
// opaque to the canonical layer); the uid is taken from Block.UID
// or freshly generated.
// or freshly generated. A simulation flag the caller sent is dropped.
// A block with a preset simulates, and any other does not.
func expandMetric(b Block) document.Block {
attrs := b.Attrs.Copy()
attrs[document.AttrUID] = resolveUID(b.UID)
delete(attrs, document.AttrSimulationActive)
if _, ok := attrs.Value(document.AttrSimulationPreset); ok {
attrs[document.AttrSimulationActive] = true
}
return document.Block{
Type: document.BlockNodeMetricBlock,
Attrs: attrs,

View File

@ -544,14 +544,46 @@ func Test_Expand(t *testing.T) {
Attrs: map[string]any{"query": "up"},
},
},
"Metric keeps an active simulation": {
"Metric naming a preset simulates": {
Input: Block{
Type: BlockMetric,
Attrs: map[string]any{"simulationPreset": "http_latency"},
},
Expected: document.Block{
Type: document.BlockNodeMetricBlock,
Attrs: map[string]any{
"simulationPreset": "http_latency",
"simulationActive": true,
},
},
},
"Metric naming a preset simulates whatever flag was sent": {
Input: Block{
Type: BlockMetric,
Attrs: map[string]any{
"simulationPreset": "http_latency",
"simulationActive": false,
},
},
Expected: document.Block{
Type: document.BlockNodeMetricBlock,
Attrs: map[string]any{
"simulationPreset": "http_latency",
"simulationActive": true,
},
},
},
"Metric naming no preset drops the flag it was sent": {
Input: Block{
Type: BlockMetric,
Attrs: map[string]any{
"simulationPreset": nil,
"simulationActive": true,
},
},
Expected: document.Block{
Type: document.BlockNodeMetricBlock,
Attrs: map[string]any{"simulationPreset": "http_latency"},
Attrs: map[string]any{"simulationPreset": nil},
},
},
// the other half of the legacy round trip: what Compact read

View File

@ -96,6 +96,56 @@ func MetricEnums() map[string][]string {
return out
}
// DeriveSimulation sets the simulation flag on an attribute update. A
// flag the caller sent is dropped. A changed preset switches the
// simulation on, and a null preset switches it off. Any other update
// leaves the stored flag alone.
func DeriveSimulation(update map[string]any, stored document.Attributes) {
delete(update, document.AttrSimulationActive)
preset, named := update[document.AttrSimulationPreset]
if !named || preset == stored[document.AttrSimulationPreset] {
return
}
update[document.AttrSimulationActive] = preset != nil
}
// KeepSimulation copies the stored simulation flag onto each metric in
// next that stored holds with the same uid and preset. A metric re-sent
// after its data arrived then does not start simulating again. It
// writes to next's attribute maps.
func KeepSimulation(next, stored document.Block) {
if next.Type != document.BlockNodeMetricBlock {
for _, child := range next.Content {
KeepSimulation(child, stored)
}
return
}
uid, ok := next.UID()
if !ok {
return
}
old, found := stored.FindByUID(uid)
if !found || old.Type != document.BlockNodeMetricBlock {
return
}
preset, ok := next.Attrs.Value(document.AttrSimulationPreset)
if !ok || preset != old.Attrs[document.AttrSimulationPreset] {
return
}
delete(next.Attrs, document.AttrSimulationActive)
if active, set := old.Attrs.Value(document.AttrSimulationActive); set {
next.Attrs[document.AttrSimulationActive] = active
}
}
// validateMetric checks a metric block: no content, and every known
// attribute it does carry is well-formed. Only present, non-null values
// are checked — the editor stores null for "unset" and an older block

View File

@ -28,6 +28,156 @@ func Test_MetricEnums(t *testing.T) {
assert.Equal(t, "line_chart", MetricEnums()[document.AttrVisualizationType][0])
}
func Test_DeriveSimulation(t *testing.T) {
t.Parallel()
cc := map[string]struct {
Update map[string]any
Stored document.Attributes
Expected map[string]any
}{
"An update naming no preset leaves the flag alone": {
Update: map[string]any{"title": "Requests"},
Stored: document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false},
Expected: map[string]any{"title": "Requests"},
},
"A flag the caller sent is dropped": {
Update: map[string]any{"title": "Requests", "simulationActive": true},
Stored: document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false},
Expected: map[string]any{"title": "Requests"},
},
"The stored preset sent again leaves the flag alone": {
Update: map[string]any{"simulationPreset": "cpu_usage"},
Stored: document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false},
Expected: map[string]any{"simulationPreset": "cpu_usage"},
},
"A first preset switches the simulation on": {
Update: map[string]any{"simulationPreset": "cpu_usage"},
Stored: document.Attributes{"simulationPreset": nil},
Expected: map[string]any{"simulationPreset": "cpu_usage", "simulationActive": true},
},
"Another preset switches the simulation on": {
Update: map[string]any{"simulationPreset": "error_rate"},
Stored: document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false},
Expected: map[string]any{"simulationPreset": "error_rate", "simulationActive": true},
},
"A null preset switches the simulation off": {
Update: map[string]any{"simulationPreset": nil},
Stored: document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true},
Expected: map[string]any{"simulationPreset": nil, "simulationActive": false},
},
"A null preset on a block that names none changes nothing": {
Update: map[string]any{"simulationPreset": nil},
Stored: document.Attributes{},
Expected: map[string]any{"simulationPreset": nil},
},
}
for cn, c := range cc {
t.Run(cn, func(t *testing.T) {
t.Parallel()
DeriveSimulation(c.Update, c.Stored)
assert.Equal(t, c.Expected, c.Update)
})
}
}
func Test_KeepSimulation(t *testing.T) {
t.Parallel()
metric := func(uid string, attrs document.Attributes) document.Block {
attrs[document.AttrUID] = uid
return document.Block{Type: document.BlockNodeMetricBlock, Attrs: attrs}
}
grid := func(items ...document.Block) document.Block {
return document.Block{Type: document.BlockNodeMetricGrid, Content: items}
}
cc := map[string]struct {
Next document.Block
Stored document.Block
Expected document.Block
}{
"The same preset keeps a simulation that was switched off": {
Next: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
Stored: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false}),
Expected: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false}),
},
"The same preset keeps a simulation that is running": {
Next: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
Stored: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
Expected: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
},
"The same preset stored without a flag drops the derived one": {
Next: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
Stored: metric("m1", document.Attributes{"simulationPreset": "cpu_usage"}),
Expected: metric("m1", document.Attributes{"simulationPreset": "cpu_usage"}),
},
"Another preset keeps what was derived": {
Next: metric("m1", document.Attributes{"simulationPreset": "error_rate", "simulationActive": true}),
Stored: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false}),
Expected: metric("m1", document.Attributes{"simulationPreset": "error_rate", "simulationActive": true}),
},
"A block naming no preset is left alone": {
Next: metric("m1", document.Attributes{"simulationPreset": nil}),
Stored: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
Expected: metric("m1", document.Attributes{"simulationPreset": nil}),
},
"A block the stored tree does not hold is left alone": {
Next: metric("m2", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
Stored: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false}),
Expected: metric("m2", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
},
"A block without a uid is left alone": {
Next: document.Block{
Type: document.BlockNodeMetricBlock,
Attrs: document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true},
},
Stored: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false}),
Expected: document.Block{
Type: document.BlockNodeMetricBlock,
Attrs: document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true},
},
},
"A uid the stored tree gives to another kind of block is left alone": {
Next: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
Stored: document.Block{
Type: document.BlockNodeParagraph,
Attrs: document.Attributes{document.AttrUID: "m1"},
},
Expected: metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
},
"A grid carries the flag of each metric it holds": {
Next: grid(
metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": true}),
metric("m2", document.Attributes{"simulationPreset": "error_rate", "simulationActive": true}),
),
Stored: grid(
metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false}),
metric("m2", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false}),
),
Expected: grid(
metric("m1", document.Attributes{"simulationPreset": "cpu_usage", "simulationActive": false}),
metric("m2", document.Attributes{"simulationPreset": "error_rate", "simulationActive": true}),
),
},
}
for cn, c := range cc {
t.Run(cn, func(t *testing.T) {
t.Parallel()
KeepSimulation(c.Next, c.Stored)
assert.Equal(t, c.Expected, c.Next)
})
}
}
func Test_validateMetric(t *testing.T) {
t.Parallel()

View File

@ -27,7 +27,7 @@ var _ BlobStore = &BlobStoreMock{}
// SetFunc: func(ctx context.Context, key string, value []byte) error {
// panic("mock out the Set method")
// },
// StartFunc: func(ctx context.Context) {
// StartFunc: func(ctx context.Context) {
// panic("mock out the Start method")
// },
// }

View File

@ -29,7 +29,7 @@ var _ HistoryStore = &HistoryStoreMock{}
// SetFunc: func(ctx context.Context, key string, value []*schema.Message) error {
// panic("mock out the Set method")
// },
// StartFunc: func(ctx context.Context) {
// StartFunc: func(ctx context.Context) {
// panic("mock out the Start method")
// },
// }

View File

@ -27,7 +27,7 @@ var _ PendingStore = &PendingStoreMock{}
// SetFunc: func(ctx context.Context, key string, value PendingConfirm) error {
// panic("mock out the Set method")
// },
// StartFunc: func(ctx context.Context) {
// StartFunc: func(ctx context.Context) {
// panic("mock out the Start method")
// },
// }

View File

@ -29,7 +29,7 @@ var _ persist.BlobStore = &BlobStore{}
// SetFunc: func(ctx context.Context, key string, value []byte) error {
// panic("mock out the Set method")
// },
// StartFunc: func(ctx context.Context) {
// StartFunc: func(ctx context.Context) {
// panic("mock out the Start method")
// },
// }

View File

@ -30,7 +30,7 @@ var _ persist.HistoryStore = &HistoryStore{}
// SetFunc: func(ctx context.Context, key string, value []*schema.Message) error {
// panic("mock out the Set method")
// },
// StartFunc: func(ctx context.Context) {
// StartFunc: func(ctx context.Context) {
// panic("mock out the Start method")
// },
// }

View File

@ -29,7 +29,7 @@ var _ persist.PendingStore = &PendingStore{}
// SetFunc: func(ctx context.Context, key string, value persist.PendingConfirm) error {
// panic("mock out the Set method")
// },
// StartFunc: func(ctx context.Context) {
// StartFunc: func(ctx context.Context) {
// panic("mock out the Start method")
// },
// }

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ var _ SearchTrigger = &SearchTriggerMock{}
//
// // make and configure a mocked SearchTrigger
// mockedSearchTrigger := &SearchTriggerMock{
// TriggerFunc: func() {
// TriggerFunc: func() {
// panic("mock out the Trigger method")
// },
// }

View File

@ -25,6 +25,9 @@ var _ Tx = &TxMock{}
// CommitFunc: func() error {
// panic("mock out the Commit method")
// },
// DeleteDocumentFunc: func(ctx context.Context, id xid.ID, organizationID string) ([]xid.ID, error) {
// panic("mock out the DeleteDocument method")
// },
// InsertDocumentFunc: func(ctx context.Context, doc document.Document) error {
// panic("mock out the InsertDocument method")
// },
@ -143,6 +146,50 @@ func (mock *TxMock) CommitCalls() []struct {
return calls
}
// DeleteDocument calls DeleteDocumentFunc.
func (mock *TxMock) DeleteDocument(ctx context.Context, id xid.ID, organizationID string) ([]xid.ID, error) {
callInfo := struct {
Ctx context.Context
ID xid.ID
OrganizationID string
}{
Ctx: ctx,
ID: id,
OrganizationID: organizationID,
}
mock.lockDeleteDocument.Lock()
mock.calls.DeleteDocument = append(mock.calls.DeleteDocument, callInfo)
mock.lockDeleteDocument.Unlock()
if mock.DeleteDocumentFunc == nil {
var (
iDsOut []xid.ID
errOut error
)
return iDsOut, errOut
}
return mock.DeleteDocumentFunc(ctx, id, organizationID)
}
// DeleteDocumentCalls gets all the calls that were made to DeleteDocument.
// Check the length with:
//
// len(mockedTx.DeleteDocumentCalls())
func (mock *TxMock) DeleteDocumentCalls() []struct {
Ctx context.Context
ID xid.ID
OrganizationID string
} {
var calls []struct {
Ctx context.Context
ID xid.ID
OrganizationID string
}
mock.lockDeleteDocument.RLock()
calls = mock.calls.DeleteDocument
mock.lockDeleteDocument.RUnlock()
return calls
}
// InsertDocument calls InsertDocumentFunc.
func (mock *TxMock) InsertDocument(ctx context.Context, doc document.Document) error {
callInfo := struct {
@ -297,47 +344,3 @@ func (mock *TxMock) UpsertDocumentMaintainersCalls() []struct {
mock.lockUpsertDocumentMaintainers.RUnlock()
return calls
}
// DeleteDocument calls DeleteDocumentFunc.
func (mock *TxMock) DeleteDocument(ctx context.Context, id xid.ID, organizationID string) ([]xid.ID, error) {
callInfo := struct {
Ctx context.Context
ID xid.ID
OrganizationID string
}{
Ctx: ctx,
ID: id,
OrganizationID: organizationID,
}
mock.lockDeleteDocument.Lock()
mock.calls.DeleteDocument = append(mock.calls.DeleteDocument, callInfo)
mock.lockDeleteDocument.Unlock()
if mock.DeleteDocumentFunc == nil {
var (
iDsOut []xid.ID
errOut error
)
return iDsOut, errOut
}
return mock.DeleteDocumentFunc(ctx, id, organizationID)
}
// DeleteDocumentCalls gets all the calls that were made to DeleteDocument.
// Check the length with:
//
// len(mockedTx.DeleteDocumentCalls())
func (mock *TxMock) DeleteDocumentCalls() []struct {
Ctx context.Context
ID xid.ID
OrganizationID string
} {
var calls []struct {
Ctx context.Context
ID xid.ID
OrganizationID string
}
mock.lockDeleteDocument.RLock()
calls = mock.calls.DeleteDocument
mock.lockDeleteDocument.RUnlock()
return calls
}

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,10 @@ package mock
import (
"context"
"sync"
"github.com/oxynote/oxynote/server/core/internal/assistant/tools"
"github.com/oxynote/oxynote/server/core/internal/search"
"sync"
)
// Ensure, that Searcher does implement tools.Searcher.
@ -18,14 +19,14 @@ var _ tools.Searcher = &Searcher{}
//
// func TestSomethingThatUsesSearcher(t *testing.T) {
//
// // make and configure a mocked Searcher
// // make and configure a mocked tools.Searcher
// mockedSearcher := &Searcher{
// SearchDocumentBlocksFunc: func(ctx context.Context, organizationID string, query string, limit int) ([]search.Block, error) {
// panic("mock out the SearchDocumentBlocks method")
// },
// }
//
// // use mockedSearcher in code that requires Searcher
// // use mockedSearcher in code that requires tools.Searcher
// // and then make assertions.
//
// }
@ -68,10 +69,10 @@ func (mock *Searcher) SearchDocumentBlocks(ctx context.Context, organizationID s
mock.lockSearchDocumentBlocks.Unlock()
if mock.SearchDocumentBlocksFunc == nil {
var (
out0 []search.Block
errOut error
blocksOut []search.Block
errOut error
)
return out0, errOut
return blocksOut, errOut
}
return mock.SearchDocumentBlocksFunc(ctx, organizationID, query, limit)
}

View File

@ -313,15 +313,11 @@ func (insertBlock) Execute(inp Input) (string, error) {
return "", fmt.Errorf("insert_block: %w", err)
}
if err := inp.CheckDataSources(in.Block.CollectAttributeValues(document.AttrDataSourceID)); err != nil {
return "", fmt.Errorf("insert_block: %w", err)
}
// uids are resolved at expansion, so expanding here rather than at
// wire time is what lets the tool report what it wrote without
// reading the document back, which the debounced persist would not
// reliably show yet.
expanded, err := block.Expand(in.Block)
expanded, err := sanitizeBlock(inp, in.Block, document.Block{})
if err != nil {
return "", fmt.Errorf("insert_block: %w", err)
}
@ -462,11 +458,20 @@ func (replaceBlock) Execute(inp Input) (string, error) {
return "", fmt.Errorf("replace_block: %w", err)
}
if err := inp.CheckDataSources(in.Block.CollectAttributeValues(document.AttrDataSourceID)); err != nil {
content, err := inp.FetchDocumentContent(in.DocumentID, in.BranchID)
if err != nil {
return "", fmt.Errorf("replace_block: %w", err)
}
expanded, err := block.Expand(in.Block)
// an unknown uid leaves stored empty, so no flag is kept. ApplyEdit
// reports the unknown uid.
var stored document.Block
if b, ok := content.Content.FindByUID(in.BlockUID); ok {
stored = b
}
expanded, err := sanitizeBlock(inp, in.Block, stored)
if err != nil {
return "", fmt.Errorf("replace_block: %w", err)
}
@ -715,15 +720,6 @@ func (updateBlockAttrs) Execute(inp Input) (string, error) {
return "", err
}
// the payload names attributes, not a block type, so a metric's
// data source arrives here on its own rather than inside a block
// the walk could find it in.
if id, ok := in.Attrs[document.AttrDataSourceID].(string); ok && id != "" {
if err := inp.CheckDataSources([]string{id}); err != nil {
return "", fmt.Errorf("update_block_attrs: %w", err)
}
}
if err := inp.ValidateAttrUpdate(in.DocumentID, in.BranchID, in.BlockUID, in.Attrs); err != nil {
return "", fmt.Errorf("update_block_attrs: %w", err)
}
@ -733,6 +729,10 @@ func (updateBlockAttrs) Execute(inp Input) (string, error) {
return "", fmt.Errorf("update_block_attrs: %w", err)
}
if err := sanitizeBlockAttrs(inp, in.Attrs, b); err != nil {
return "", fmt.Errorf("update_block_attrs: %w", err)
}
if err := inp.ApplyEdit(in.DocumentID, in.BranchID, []edit.Operation{edit.UpdateAttrs(in.BlockUID, in.Attrs)}); err != nil {
return "", err
}
@ -998,3 +998,41 @@ func (moveBlock) Execute(inp Input) (string, error) {
return result(blockWriteResult{Blocks: blockRows(b)})
}
// sanitizeBlock checks a block before a write and expands it. stored is
// the block being replaced, or empty for an insert. A metric that stored
// already holds keeps its simulation flag.
func sanitizeBlock(inp Input, b block.Block, stored document.Block) (document.Block, error) {
if err := inp.CheckDataSources(b.CollectAttributeValues(document.AttrDataSourceID)); err != nil {
return document.Block{}, err
}
expanded, err := block.Expand(b)
if err != nil {
return document.Block{}, err
}
block.KeepSimulation(expanded, stored)
return expanded, nil
}
// sanitizeBlockAttrs checks an attribute update by the type of the
// stored block. For a metric it checks the data source and sets the
// simulation flag. It writes to attrs.
func sanitizeBlockAttrs(inp Input, attrs map[string]any, stored document.Block) error {
if stored.Type != document.BlockNodeMetricBlock {
return nil
}
// an empty data source means unset, so there is nothing to check.
if id, ok := attrs[document.AttrDataSourceID].(string); ok && id != "" {
if err := inp.CheckDataSources([]string{id}); err != nil {
return err
}
}
block.DeriveSimulation(attrs, stored.Attrs)
return nil
}

View File

@ -2,6 +2,7 @@ package tools
import (
"context"
"encoding/json"
"testing"
"github.com/oxynote/oxynote/server/core/internal/assistant/block"
@ -26,10 +27,13 @@ const _stubContentUID = "a"
// for the cases that need a block with attributes of its own.
const _stubHeadingUID = "h"
// _stubMetricUID is the uid of the metric stubSimulationDB answers with.
const _stubMetricUID = "m"
// stubContentDB answers content reads with a single paragraph the
// placement checks can find.
func stubContentDB(err error) *DBMock {
root := document.RootBlock{
return stubRootDB(document.RootBlock{
Content: []document.Block{
{
Type: document.BlockNodeParagraph,
@ -42,8 +46,35 @@ func stubContentDB(err error) *DBMock {
Attrs: document.Attributes{document.AttrUID: _stubHeadingUID, document.AttrLevel: 1},
},
},
}
}, err)
}
// stubSimulationDB answers content reads with one metric whose data has
// arrived: it still names its preset and its simulation is switched off.
func stubSimulationDB() *DBMock {
return stubRootDB(document.RootBlock{
Content: []document.Block{
{
Type: document.BlockNodeMetricGrid,
Attrs: document.Attributes{document.AttrUID: "g"},
Content: []document.Block{
{
Type: document.BlockNodeMetricBlock,
Attrs: document.Attributes{
document.AttrUID: _stubMetricUID,
document.AttrSimulationPreset: "cpu_usage",
document.AttrSimulationActive: false,
},
},
},
},
},
}, nil)
}
// stubRootDB answers content reads with the given tree. err makes the
// branch fetch fail.
func stubRootDB(root document.RootBlock, err error) *DBMock {
db := stubDocumentDB()
fetch := db.FetchDocumentFunc
fetchByBranch := db.FetchDocumentByBranchIDFunc
@ -98,7 +129,11 @@ func metricBlockArgs(dataSourceID string) string {
// stubMetricDB answers content reads and resolves exactly one data
// source, so only the id under test decides a metric write's outcome.
func stubMetricDB() *DBMock {
db := stubContentDB(nil)
return stubDataSource(stubContentDB(nil))
}
// stubDataSource makes db resolve exactly one data source.
func stubDataSource(db *DBMock) *DBMock {
db.FetchDataSourceFunc = func(_ context.Context, id xid.ID, orgID string) (*datasource.DataSource, error) {
if id != _testDataSourceID || orgID != "org" {
return nil, errutil.ErrNotFound
@ -120,7 +155,12 @@ type editCase struct {
Runner *datasourceMock.Runner
Args string
Contains []string
Err error
// Op is the single operation the write should send, as the realtime
// service receives it. Empty skips the check.
Op string
Err error
}
// runEdit executes a block tool and asserts the shared outcome.
@ -153,6 +193,21 @@ func runEdit(t *testing.T, tl Tool, name Name, c editCase) {
for _, want := range c.Contains {
assert.Contains(t, res, want)
}
if c.Op == "" {
return
}
require.Len(t, applier.ApplyCalls(), 1)
require.Len(t, applier.ApplyCalls()[0].Ops, 1)
op, err := applier.ApplyCalls()[0].Ops[0]()
require.NoError(t, err)
raw, err := json.Marshal(op)
require.NoError(t, err)
assert.JSONEq(t, c.Op, string(raw))
}
func Test_readBlockArgs_Validate(t *testing.T) {
@ -540,6 +595,22 @@ func Test_replaceBlock_Execute(t *testing.T) {
`","block":` + metricBlockArgs(_unknownDataSourceID) + `}`,
Err: assert.AnError,
},
// read_block shows a metric with its preset even after its data
// arrived. Re-sending it must not start the simulation again.
"A metric re-sent with its preset keeps its simulation switched off": {
DB: stubSimulationDB(),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"` + _stubMetricUID + `","block":` +
`{"type":"metric","uid":"` + _stubMetricUID + `","attrs":{"title":"CPU","simulationPreset":"cpu_usage"}}}`,
Op: `{"kind":"replace","block_uid":"m","block":{"type":"metricBlock","attrs":` +
`{"uid":"m","title":"CPU","simulationPreset":"cpu_usage","simulationActive":false}}}`,
},
"A metric re-sent with another preset simulates": {
DB: stubSimulationDB(),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"` + _stubMetricUID + `","block":` +
`{"type":"metric","uid":"` + _stubMetricUID + `","attrs":{"simulationPreset":"error_rate"}}}`,
Op: `{"kind":"replace","block_uid":"m","block":{"type":"metricBlock","attrs":` +
`{"uid":"m","simulationPreset":"error_rate","simulationActive":true}}}`,
},
}
for cn, c := range cc {
@ -757,21 +828,46 @@ func Test_updateBlockAttrs_Execute(t *testing.T) {
// the payload names attributes, not a block type, so a metric's
// data source arrives on its own rather than inside a block.
"A data source the organisation owns": {
DB: stubMetricDB(),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"a","attrs":{"dataSourceId":"` +
DB: stubDataSource(stubSimulationDB()),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"` + _stubMetricUID + `","attrs":{"dataSourceId":"` +
_testDataSourceID.String() + `"}}`,
},
"A data source it does not": {
DB: stubMetricDB(),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"a","attrs":{"dataSourceId":"` +
DB: stubDataSource(stubSimulationDB()),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"` + _stubMetricUID + `","attrs":{"dataSourceId":"` +
_unknownDataSourceID + `"}}`,
Err: assert.AnError,
},
// an empty data source is the editor's "unset", not a reference
// to check, and no other attribute names one at all.
"An empty data source is not looked up": {
DB: stubMetricDB(),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"a","attrs":{"dataSourceId":""}}`,
DB: stubDataSource(stubSimulationDB()),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"` + _stubMetricUID + `","attrs":{"dataSourceId":""}}`,
},
"A simulation flag the caller sent is dropped": {
DB: stubSimulationDB(),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"` + _stubMetricUID +
`","attrs":{"title":"CPU","simulationActive":true}}`,
Op: `{"kind":"update_attrs","block_uid":"m","attrs":{"title":"CPU"}}`,
},
"Another preset switches the simulation on": {
DB: stubSimulationDB(),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"` + _stubMetricUID +
`","attrs":{"simulationPreset":"error_rate"}}`,
Op: `{"kind":"update_attrs","block_uid":"m","attrs":{"simulationPreset":"error_rate","simulationActive":true}}`,
},
"A null preset switches the simulation off": {
DB: stubSimulationDB(),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"` + _stubMetricUID +
`","attrs":{"simulationPreset":null}}`,
Op: `{"kind":"update_attrs","block_uid":"m","attrs":{"simulationPreset":null,"simulationActive":false}}`,
},
// only metric blocks have the flag, so any other block is sent
// unchanged.
"A block that is not a metric is sent as given": {
DB: stubContentDB(nil),
Args: `{` + targetArgs(_stubMainBranchID) + `,"block_uid":"h","attrs":{"level":2}}`,
Op: `{"kind":"update_attrs","block_uid":"h","attrs":{"level":2}}`,
},
}
@ -1000,6 +1096,144 @@ func Test_moveBlock_Execute(t *testing.T) {
}
}
func Test_sanitizeBlock(t *testing.T) {
t.Parallel()
metric := func(attrs document.Attributes) block.Block {
return block.Block{Type: block.BlockMetric, UID: _stubMetricUID, Attrs: attrs}
}
cc := map[string]struct {
Block block.Block
Stored document.Block
Result document.Attributes
Err error
}{
"Error returned by inp.CheckDataSources": {
Block: metric(document.Attributes{document.AttrDataSourceID: _unknownDataSourceID}),
Err: assert.AnError,
},
"Error returned by block.Expand": {
Block: block.Block{Type: "nope"},
Err: assert.AnError,
},
"A new metric naming a preset simulates": {
Block: metric(document.Attributes{document.AttrSimulationPreset: "cpu_usage"}),
Result: document.Attributes{
document.AttrUID: _stubMetricUID,
document.AttrSimulationPreset: "cpu_usage",
document.AttrSimulationActive: true,
},
},
"A stored metric keeps its simulation flag": {
Block: metric(document.Attributes{document.AttrSimulationPreset: "cpu_usage"}),
Stored: document.Block{
Type: document.BlockNodeMetricBlock,
Attrs: document.Attributes{
document.AttrUID: _stubMetricUID,
document.AttrSimulationPreset: "cpu_usage",
document.AttrSimulationActive: false,
},
},
Result: document.Attributes{
document.AttrUID: _stubMetricUID,
document.AttrSimulationPreset: "cpu_usage",
document.AttrSimulationActive: false,
},
},
}
for cn, c := range cc {
t.Run(cn, func(t *testing.T) {
t.Parallel()
inp := testInput(testDeps(stubMetricDB(), nil, nil), NameReplaceBlock, `{}`)
res, err := sanitizeBlock(inp, c.Block, c.Stored)
testutil.AssertEqualError(t, c.Err, err)
if err != nil {
return
}
assert.Equal(t, document.BlockNodeMetricBlock, res.Type)
assert.Equal(t, c.Result, res.Attrs)
})
}
}
func Test_sanitizeBlockAttrs(t *testing.T) {
t.Parallel()
stored := document.Block{
Type: document.BlockNodeMetricBlock,
Attrs: document.Attributes{
document.AttrUID: _stubMetricUID,
document.AttrSimulationPreset: "cpu_usage",
document.AttrSimulationActive: false,
},
}
cc := map[string]struct {
Attrs map[string]any
Stored document.Block
Result map[string]any
Err error
}{
"Error returned by inp.CheckDataSources": {
Attrs: map[string]any{document.AttrDataSourceID: _unknownDataSourceID},
Stored: stored,
Err: assert.AnError,
},
"A block that is not a metric is left as given": {
Attrs: map[string]any{
document.AttrDataSourceID: _unknownDataSourceID,
document.AttrSimulationActive: true,
},
Stored: document.Block{Type: document.BlockNodeParagraph},
Result: map[string]any{
document.AttrDataSourceID: _unknownDataSourceID,
document.AttrSimulationActive: true,
},
},
"An empty data source is not looked up": {
Attrs: map[string]any{document.AttrDataSourceID: ""},
Stored: stored,
Result: map[string]any{document.AttrDataSourceID: ""},
},
"A metric has its simulation flag derived": {
Attrs: map[string]any{
document.AttrDataSourceID: _testDataSourceID.String(),
document.AttrSimulationPreset: "error_rate",
document.AttrSimulationActive: false,
},
Stored: stored,
Result: map[string]any{
document.AttrDataSourceID: _testDataSourceID.String(),
document.AttrSimulationPreset: "error_rate",
document.AttrSimulationActive: true,
},
},
}
for cn, c := range cc {
t.Run(cn, func(t *testing.T) {
t.Parallel()
inp := testInput(testDeps(stubMetricDB(), nil, nil), NameUpdateBlockAttrs, `{}`)
err := sanitizeBlockAttrs(inp, c.Attrs, c.Stored)
testutil.AssertEqualError(t, c.Err, err)
if err != nil {
return
}
assert.Equal(t, c.Result, c.Attrs)
})
}
}
func Test_docTarget_validate(t *testing.T) {
t.Parallel()

View File

@ -40,8 +40,8 @@ type StatusStoreMock struct {
UpdateDataSourceStatus []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Id is the id argument value.
Id xid.ID
// ID is the id argument value.
ID xid.ID
// OrganizationID is the organizationID argument value.
OrganizationID string
// Status is the status argument value.
@ -55,12 +55,12 @@ type StatusStoreMock struct {
func (mock *StatusStoreMock) UpdateDataSourceStatus(ctx context.Context, id xid.ID, organizationID string, status processor.ConnectionStatus) error {
callInfo := struct {
Ctx context.Context
Id xid.ID
ID xid.ID
OrganizationID string
Status processor.ConnectionStatus
}{
Ctx: ctx,
Id: id,
ID: id,
OrganizationID: organizationID,
Status: status,
}
@ -82,13 +82,13 @@ func (mock *StatusStoreMock) UpdateDataSourceStatus(ctx context.Context, id xid.
// len(mockedStatusStore.UpdateDataSourceStatusCalls())
func (mock *StatusStoreMock) UpdateDataSourceStatusCalls() []struct {
Ctx context.Context
Id xid.ID
ID xid.ID
OrganizationID string
Status processor.ConnectionStatus
} {
var calls []struct {
Ctx context.Context
Id xid.ID
ID xid.ID
OrganizationID string
Status processor.ConnectionStatus
}

View File

@ -17,62 +17,48 @@ var _ datasource.MySQL = &MySQL{}
// MySQL is a mock implementation of datasource.MySQL.
//
// func TestSomethingThatUsesdatasource.MySQL(t *testing.T) {
// func TestSomethingThatUsesMySQL(t *testing.T) {
//
// // make and configure a mocked datasource.MySQL
// mockeddatasource.MySQL := &MySQL{
// TestConnectionFunc: func(ctx context.Context) (processor.ConnectionStatus, error) {
// panic("mock out the TestConnection method")
// },
// mockedMySQL := &MySQL{
// MetadataFunc: func(ctx context.Context) (*processor.SQLMetadataResult, error) {
// panic("mock out the Metadata method")
// },
// QueryLabelsFunc: func(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error) {
// panic("mock out the QueryLabels method")
// },
// QueryFunc: func(ctx context.Context, q string, tr processor.TimeRange) (*processor.MySQLQueryResult, error) {
// panic("mock out the Query method")
// },
// QueryLabelsFunc: func(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error) {
// panic("mock out the QueryLabels method")
// },
// TestConnectionFunc: func(ctx context.Context) (processor.ConnectionStatus, error) {
// panic("mock out the TestConnection method")
// },
// }
//
// // use mockeddatasource.MySQL in code that requires datasource.MySQL
// // use mockedMySQL in code that requires datasource.MySQL
// // and then make assertions.
//
// }
type MySQL struct {
// TestConnectionFunc mocks the TestConnection method.
TestConnectionFunc func(ctx context.Context) (processor.ConnectionStatus, error)
// MetadataFunc mocks the Metadata method.
MetadataFunc func(ctx context.Context) (*processor.SQLMetadataResult, error)
// QueryLabelsFunc mocks the QueryLabels method.
QueryLabelsFunc func(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error)
// QueryFunc mocks the Query method.
QueryFunc func(ctx context.Context, q string, tr processor.TimeRange) (*processor.MySQLQueryResult, error)
// QueryLabelsFunc mocks the QueryLabels method.
QueryLabelsFunc func(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error)
// TestConnectionFunc mocks the TestConnection method.
TestConnectionFunc func(ctx context.Context) (processor.ConnectionStatus, error)
// calls tracks calls to the methods.
calls struct {
// TestConnection holds details about calls to the TestConnection method.
TestConnection []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// Metadata holds details about calls to the Metadata method.
Metadata []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// QueryLabels holds details about calls to the QueryLabels method.
QueryLabels []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Q is the q argument value.
Q string
// Tr is the tr argument value.
Tr processor.TimeRange
}
// Query holds details about calls to the Query method.
Query []struct {
// Ctx is the ctx argument value.
@ -82,47 +68,25 @@ type MySQL struct {
// Tr is the tr argument value.
Tr processor.TimeRange
}
// QueryLabels holds details about calls to the QueryLabels method.
QueryLabels []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Q is the q argument value.
Q string
// Tr is the tr argument value.
Tr processor.TimeRange
}
// TestConnection holds details about calls to the TestConnection method.
TestConnection []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
}
lockTestConnection sync.RWMutex
lockMetadata sync.RWMutex
lockQueryLabels sync.RWMutex
lockQuery sync.RWMutex
}
// TestConnection calls TestConnectionFunc.
func (mock *MySQL) TestConnection(ctx context.Context) (processor.ConnectionStatus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockTestConnection.Lock()
mock.calls.TestConnection = append(mock.calls.TestConnection, callInfo)
mock.lockTestConnection.Unlock()
if mock.TestConnectionFunc == nil {
var (
connectionStatusOut processor.ConnectionStatus
errOut error
)
return connectionStatusOut, errOut
}
return mock.TestConnectionFunc(ctx)
}
// TestConnectionCalls gets all the calls that were made to TestConnection.
// Check the length with:
//
// len(mockeddatasource.MySQL.TestConnectionCalls())
func (mock *MySQL) TestConnectionCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockTestConnection.RLock()
calls = mock.calls.TestConnection
mock.lockTestConnection.RUnlock()
return calls
lockQueryLabels sync.RWMutex
lockTestConnection sync.RWMutex
}
// Metadata calls MetadataFunc.
@ -148,7 +112,7 @@ func (mock *MySQL) Metadata(ctx context.Context) (*processor.SQLMetadataResult,
// MetadataCalls gets all the calls that were made to Metadata.
// Check the length with:
//
// len(mockeddatasource.MySQL.MetadataCalls())
// len(mockedMySQL.MetadataCalls())
func (mock *MySQL) MetadataCalls() []struct {
Ctx context.Context
} {
@ -161,50 +125,6 @@ func (mock *MySQL) MetadataCalls() []struct {
return calls
}
// QueryLabels calls QueryLabelsFunc.
func (mock *MySQL) QueryLabels(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error) {
callInfo := struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}{
Ctx: ctx,
Q: q,
Tr: tr,
}
mock.lockQueryLabels.Lock()
mock.calls.QueryLabels = append(mock.calls.QueryLabels, callInfo)
mock.lockQueryLabels.Unlock()
if mock.QueryLabelsFunc == nil {
var (
stringToStringOut map[string]string
errOut error
)
return stringToStringOut, errOut
}
return mock.QueryLabelsFunc(ctx, q, tr)
}
// QueryLabelsCalls gets all the calls that were made to QueryLabels.
// Check the length with:
//
// len(mockeddatasource.MySQL.QueryLabelsCalls())
func (mock *MySQL) QueryLabelsCalls() []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
} {
var calls []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}
mock.lockQueryLabels.RLock()
calls = mock.calls.QueryLabels
mock.lockQueryLabels.RUnlock()
return calls
}
// Query calls QueryFunc.
func (mock *MySQL) Query(ctx context.Context, q string, tr processor.TimeRange) (*processor.MySQLQueryResult, error) {
callInfo := struct {
@ -232,7 +152,7 @@ func (mock *MySQL) Query(ctx context.Context, q string, tr processor.TimeRange)
// QueryCalls gets all the calls that were made to Query.
// Check the length with:
//
// len(mockeddatasource.MySQL.QueryCalls())
// len(mockedMySQL.QueryCalls())
func (mock *MySQL) QueryCalls() []struct {
Ctx context.Context
Q string
@ -248,3 +168,83 @@ func (mock *MySQL) QueryCalls() []struct {
mock.lockQuery.RUnlock()
return calls
}
// QueryLabels calls QueryLabelsFunc.
func (mock *MySQL) QueryLabels(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error) {
callInfo := struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}{
Ctx: ctx,
Q: q,
Tr: tr,
}
mock.lockQueryLabels.Lock()
mock.calls.QueryLabels = append(mock.calls.QueryLabels, callInfo)
mock.lockQueryLabels.Unlock()
if mock.QueryLabelsFunc == nil {
var (
stringToStringOut map[string]string
errOut error
)
return stringToStringOut, errOut
}
return mock.QueryLabelsFunc(ctx, q, tr)
}
// QueryLabelsCalls gets all the calls that were made to QueryLabels.
// Check the length with:
//
// len(mockedMySQL.QueryLabelsCalls())
func (mock *MySQL) QueryLabelsCalls() []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
} {
var calls []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}
mock.lockQueryLabels.RLock()
calls = mock.calls.QueryLabels
mock.lockQueryLabels.RUnlock()
return calls
}
// TestConnection calls TestConnectionFunc.
func (mock *MySQL) TestConnection(ctx context.Context) (processor.ConnectionStatus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockTestConnection.Lock()
mock.calls.TestConnection = append(mock.calls.TestConnection, callInfo)
mock.lockTestConnection.Unlock()
if mock.TestConnectionFunc == nil {
var (
connectionStatusOut processor.ConnectionStatus
errOut error
)
return connectionStatusOut, errOut
}
return mock.TestConnectionFunc(ctx)
}
// TestConnectionCalls gets all the calls that were made to TestConnection.
// Check the length with:
//
// len(mockedMySQL.TestConnectionCalls())
func (mock *MySQL) TestConnectionCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockTestConnection.RLock()
calls = mock.calls.TestConnection
mock.lockTestConnection.RUnlock()
return calls
}

View File

@ -17,62 +17,48 @@ var _ datasource.PostgreSQL = &PostgreSQL{}
// PostgreSQL is a mock implementation of datasource.PostgreSQL.
//
// func TestSomethingThatUsesdatasource.PostgreSQL(t *testing.T) {
// func TestSomethingThatUsesPostgreSQL(t *testing.T) {
//
// // make and configure a mocked datasource.PostgreSQL
// mockeddatasource.PostgreSQL := &PostgreSQL{
// TestConnectionFunc: func(ctx context.Context) (processor.ConnectionStatus, error) {
// panic("mock out the TestConnection method")
// },
// mockedPostgreSQL := &PostgreSQL{
// MetadataFunc: func(ctx context.Context) (*processor.SQLMetadataResult, error) {
// panic("mock out the Metadata method")
// },
// QueryLabelsFunc: func(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error) {
// panic("mock out the QueryLabels method")
// },
// QueryFunc: func(ctx context.Context, q string, tr processor.TimeRange) (*processor.PostgreSQLQueryResult, error) {
// panic("mock out the Query method")
// },
// QueryLabelsFunc: func(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error) {
// panic("mock out the QueryLabels method")
// },
// TestConnectionFunc: func(ctx context.Context) (processor.ConnectionStatus, error) {
// panic("mock out the TestConnection method")
// },
// }
//
// // use mockeddatasource.PostgreSQL in code that requires datasource.PostgreSQL
// // use mockedPostgreSQL in code that requires datasource.PostgreSQL
// // and then make assertions.
//
// }
type PostgreSQL struct {
// TestConnectionFunc mocks the TestConnection method.
TestConnectionFunc func(ctx context.Context) (processor.ConnectionStatus, error)
// MetadataFunc mocks the Metadata method.
MetadataFunc func(ctx context.Context) (*processor.SQLMetadataResult, error)
// QueryLabelsFunc mocks the QueryLabels method.
QueryLabelsFunc func(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error)
// QueryFunc mocks the Query method.
QueryFunc func(ctx context.Context, q string, tr processor.TimeRange) (*processor.PostgreSQLQueryResult, error)
// QueryLabelsFunc mocks the QueryLabels method.
QueryLabelsFunc func(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error)
// TestConnectionFunc mocks the TestConnection method.
TestConnectionFunc func(ctx context.Context) (processor.ConnectionStatus, error)
// calls tracks calls to the methods.
calls struct {
// TestConnection holds details about calls to the TestConnection method.
TestConnection []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// Metadata holds details about calls to the Metadata method.
Metadata []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// QueryLabels holds details about calls to the QueryLabels method.
QueryLabels []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Q is the q argument value.
Q string
// Tr is the tr argument value.
Tr processor.TimeRange
}
// Query holds details about calls to the Query method.
Query []struct {
// Ctx is the ctx argument value.
@ -82,47 +68,25 @@ type PostgreSQL struct {
// Tr is the tr argument value.
Tr processor.TimeRange
}
// QueryLabels holds details about calls to the QueryLabels method.
QueryLabels []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Q is the q argument value.
Q string
// Tr is the tr argument value.
Tr processor.TimeRange
}
// TestConnection holds details about calls to the TestConnection method.
TestConnection []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
}
lockTestConnection sync.RWMutex
lockMetadata sync.RWMutex
lockQueryLabels sync.RWMutex
lockQuery sync.RWMutex
}
// TestConnection calls TestConnectionFunc.
func (mock *PostgreSQL) TestConnection(ctx context.Context) (processor.ConnectionStatus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockTestConnection.Lock()
mock.calls.TestConnection = append(mock.calls.TestConnection, callInfo)
mock.lockTestConnection.Unlock()
if mock.TestConnectionFunc == nil {
var (
connectionStatusOut processor.ConnectionStatus
errOut error
)
return connectionStatusOut, errOut
}
return mock.TestConnectionFunc(ctx)
}
// TestConnectionCalls gets all the calls that were made to TestConnection.
// Check the length with:
//
// len(mockeddatasource.PostgreSQL.TestConnectionCalls())
func (mock *PostgreSQL) TestConnectionCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockTestConnection.RLock()
calls = mock.calls.TestConnection
mock.lockTestConnection.RUnlock()
return calls
lockQueryLabels sync.RWMutex
lockTestConnection sync.RWMutex
}
// Metadata calls MetadataFunc.
@ -148,7 +112,7 @@ func (mock *PostgreSQL) Metadata(ctx context.Context) (*processor.SQLMetadataRes
// MetadataCalls gets all the calls that were made to Metadata.
// Check the length with:
//
// len(mockeddatasource.PostgreSQL.MetadataCalls())
// len(mockedPostgreSQL.MetadataCalls())
func (mock *PostgreSQL) MetadataCalls() []struct {
Ctx context.Context
} {
@ -161,50 +125,6 @@ func (mock *PostgreSQL) MetadataCalls() []struct {
return calls
}
// QueryLabels calls QueryLabelsFunc.
func (mock *PostgreSQL) QueryLabels(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error) {
callInfo := struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}{
Ctx: ctx,
Q: q,
Tr: tr,
}
mock.lockQueryLabels.Lock()
mock.calls.QueryLabels = append(mock.calls.QueryLabels, callInfo)
mock.lockQueryLabels.Unlock()
if mock.QueryLabelsFunc == nil {
var (
stringToStringOut map[string]string
errOut error
)
return stringToStringOut, errOut
}
return mock.QueryLabelsFunc(ctx, q, tr)
}
// QueryLabelsCalls gets all the calls that were made to QueryLabels.
// Check the length with:
//
// len(mockeddatasource.PostgreSQL.QueryLabelsCalls())
func (mock *PostgreSQL) QueryLabelsCalls() []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
} {
var calls []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}
mock.lockQueryLabels.RLock()
calls = mock.calls.QueryLabels
mock.lockQueryLabels.RUnlock()
return calls
}
// Query calls QueryFunc.
func (mock *PostgreSQL) Query(ctx context.Context, q string, tr processor.TimeRange) (*processor.PostgreSQLQueryResult, error) {
callInfo := struct {
@ -232,7 +152,7 @@ func (mock *PostgreSQL) Query(ctx context.Context, q string, tr processor.TimeRa
// QueryCalls gets all the calls that were made to Query.
// Check the length with:
//
// len(mockeddatasource.PostgreSQL.QueryCalls())
// len(mockedPostgreSQL.QueryCalls())
func (mock *PostgreSQL) QueryCalls() []struct {
Ctx context.Context
Q string
@ -248,3 +168,83 @@ func (mock *PostgreSQL) QueryCalls() []struct {
mock.lockQuery.RUnlock()
return calls
}
// QueryLabels calls QueryLabelsFunc.
func (mock *PostgreSQL) QueryLabels(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error) {
callInfo := struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}{
Ctx: ctx,
Q: q,
Tr: tr,
}
mock.lockQueryLabels.Lock()
mock.calls.QueryLabels = append(mock.calls.QueryLabels, callInfo)
mock.lockQueryLabels.Unlock()
if mock.QueryLabelsFunc == nil {
var (
stringToStringOut map[string]string
errOut error
)
return stringToStringOut, errOut
}
return mock.QueryLabelsFunc(ctx, q, tr)
}
// QueryLabelsCalls gets all the calls that were made to QueryLabels.
// Check the length with:
//
// len(mockedPostgreSQL.QueryLabelsCalls())
func (mock *PostgreSQL) QueryLabelsCalls() []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
} {
var calls []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}
mock.lockQueryLabels.RLock()
calls = mock.calls.QueryLabels
mock.lockQueryLabels.RUnlock()
return calls
}
// TestConnection calls TestConnectionFunc.
func (mock *PostgreSQL) TestConnection(ctx context.Context) (processor.ConnectionStatus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockTestConnection.Lock()
mock.calls.TestConnection = append(mock.calls.TestConnection, callInfo)
mock.lockTestConnection.Unlock()
if mock.TestConnectionFunc == nil {
var (
connectionStatusOut processor.ConnectionStatus
errOut error
)
return connectionStatusOut, errOut
}
return mock.TestConnectionFunc(ctx)
}
// TestConnectionCalls gets all the calls that were made to TestConnection.
// Check the length with:
//
// len(mockedPostgreSQL.TestConnectionCalls())
func (mock *PostgreSQL) TestConnectionCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockTestConnection.RLock()
calls = mock.calls.TestConnection
mock.lockTestConnection.RUnlock()
return calls
}

View File

@ -17,12 +17,15 @@ var _ datasource.Prometheus = &Prometheus{}
// Prometheus is a mock implementation of datasource.Prometheus.
//
// func TestSomethingThatUsesdatasource.Prometheus(t *testing.T) {
// func TestSomethingThatUsesPrometheus(t *testing.T) {
//
// // make and configure a mocked datasource.Prometheus
// mockeddatasource.Prometheus := &Prometheus{
// TestConnectionFunc: func(ctx context.Context) (processor.ConnectionStatus, error) {
// panic("mock out the TestConnection method")
// mockedPrometheus := &Prometheus{
// LabelNamesFunc: func(ctx context.Context, matchers []string, tr processor.TimeRange) (*processor.PrometheusLabelNamesResult, error) {
// panic("mock out the LabelNames method")
// },
// LabelValuesFunc: func(ctx context.Context, label string, matchers []string, tr processor.TimeRange) (*processor.PrometheusLabelValuesResult, error) {
// panic("mock out the LabelValues method")
// },
// MetadataFunc: func(ctx context.Context) (*processor.PrometheusMetadataResult, error) {
// panic("mock out the Metadata method")
@ -30,24 +33,24 @@ var _ datasource.Prometheus = &Prometheus{}
// QueryRangeFunc: func(ctx context.Context, q string, tr processor.TimeRange) (*processor.PrometheusQueryResult, error) {
// panic("mock out the QueryRange method")
// },
// LabelNamesFunc: func(ctx context.Context, matchers []string, tr processor.TimeRange) (*processor.PrometheusLabelNamesResult, error) {
// panic("mock out the LabelNames method")
// },
// LabelValuesFunc: func(ctx context.Context, label string, matchers []string, tr processor.TimeRange) (*processor.PrometheusLabelValuesResult, error) {
// panic("mock out the LabelValues method")
// },
// SeriesFunc: func(ctx context.Context, matchers []string, tr processor.TimeRange) (*processor.PrometheusSeriesResult, error) {
// panic("mock out the Series method")
// },
// TestConnectionFunc: func(ctx context.Context) (processor.ConnectionStatus, error) {
// panic("mock out the TestConnection method")
// },
// }
//
// // use mockeddatasource.Prometheus in code that requires datasource.Prometheus
// // use mockedPrometheus in code that requires datasource.Prometheus
// // and then make assertions.
//
// }
type Prometheus struct {
// TestConnectionFunc mocks the TestConnection method.
TestConnectionFunc func(ctx context.Context) (processor.ConnectionStatus, error)
// LabelNamesFunc mocks the LabelNames method.
LabelNamesFunc func(ctx context.Context, matchers []string, tr processor.TimeRange) (*processor.PrometheusLabelNamesResult, error)
// LabelValuesFunc mocks the LabelValues method.
LabelValuesFunc func(ctx context.Context, label string, matchers []string, tr processor.TimeRange) (*processor.PrometheusLabelValuesResult, error)
// MetadataFunc mocks the Metadata method.
MetadataFunc func(ctx context.Context) (*processor.PrometheusMetadataResult, error)
@ -55,36 +58,14 @@ type Prometheus struct {
// QueryRangeFunc mocks the QueryRange method.
QueryRangeFunc func(ctx context.Context, q string, tr processor.TimeRange) (*processor.PrometheusQueryResult, error)
// LabelNamesFunc mocks the LabelNames method.
LabelNamesFunc func(ctx context.Context, matchers []string, tr processor.TimeRange) (*processor.PrometheusLabelNamesResult, error)
// LabelValuesFunc mocks the LabelValues method.
LabelValuesFunc func(ctx context.Context, label string, matchers []string, tr processor.TimeRange) (*processor.PrometheusLabelValuesResult, error)
// SeriesFunc mocks the Series method.
SeriesFunc func(ctx context.Context, matchers []string, tr processor.TimeRange) (*processor.PrometheusSeriesResult, error)
// TestConnectionFunc mocks the TestConnection method.
TestConnectionFunc func(ctx context.Context) (processor.ConnectionStatus, error)
// calls tracks calls to the methods.
calls struct {
// TestConnection holds details about calls to the TestConnection method.
TestConnection []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// Metadata holds details about calls to the Metadata method.
Metadata []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// QueryRange holds details about calls to the QueryRange method.
QueryRange []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Q is the q argument value.
Q string
// Tr is the tr argument value.
Tr processor.TimeRange
}
// LabelNames holds details about calls to the LabelNames method.
LabelNames []struct {
// Ctx is the ctx argument value.
@ -105,6 +86,20 @@ type Prometheus struct {
// Tr is the tr argument value.
Tr processor.TimeRange
}
// Metadata holds details about calls to the Metadata method.
Metadata []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// QueryRange holds details about calls to the QueryRange method.
QueryRange []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Q is the q argument value.
Q string
// Tr is the tr argument value.
Tr processor.TimeRange
}
// Series holds details about calls to the Series method.
Series []struct {
// Ctx is the ctx argument value.
@ -114,129 +109,18 @@ type Prometheus struct {
// Tr is the tr argument value.
Tr processor.TimeRange
}
// TestConnection holds details about calls to the TestConnection method.
TestConnection []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
}
lockTestConnection sync.RWMutex
lockMetadata sync.RWMutex
lockQueryRange sync.RWMutex
lockLabelNames sync.RWMutex
lockLabelValues sync.RWMutex
lockMetadata sync.RWMutex
lockQueryRange sync.RWMutex
lockSeries sync.RWMutex
}
// TestConnection calls TestConnectionFunc.
func (mock *Prometheus) TestConnection(ctx context.Context) (processor.ConnectionStatus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockTestConnection.Lock()
mock.calls.TestConnection = append(mock.calls.TestConnection, callInfo)
mock.lockTestConnection.Unlock()
if mock.TestConnectionFunc == nil {
var (
connectionStatusOut processor.ConnectionStatus
errOut error
)
return connectionStatusOut, errOut
}
return mock.TestConnectionFunc(ctx)
}
// TestConnectionCalls gets all the calls that were made to TestConnection.
// Check the length with:
//
// len(mockeddatasource.Prometheus.TestConnectionCalls())
func (mock *Prometheus) TestConnectionCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockTestConnection.RLock()
calls = mock.calls.TestConnection
mock.lockTestConnection.RUnlock()
return calls
}
// Metadata calls MetadataFunc.
func (mock *Prometheus) Metadata(ctx context.Context) (*processor.PrometheusMetadataResult, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockMetadata.Lock()
mock.calls.Metadata = append(mock.calls.Metadata, callInfo)
mock.lockMetadata.Unlock()
if mock.MetadataFunc == nil {
var (
prometheusMetadataResultOut *processor.PrometheusMetadataResult
errOut error
)
return prometheusMetadataResultOut, errOut
}
return mock.MetadataFunc(ctx)
}
// MetadataCalls gets all the calls that were made to Metadata.
// Check the length with:
//
// len(mockeddatasource.Prometheus.MetadataCalls())
func (mock *Prometheus) MetadataCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockMetadata.RLock()
calls = mock.calls.Metadata
mock.lockMetadata.RUnlock()
return calls
}
// QueryRange calls QueryRangeFunc.
func (mock *Prometheus) QueryRange(ctx context.Context, q string, tr processor.TimeRange) (*processor.PrometheusQueryResult, error) {
callInfo := struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}{
Ctx: ctx,
Q: q,
Tr: tr,
}
mock.lockQueryRange.Lock()
mock.calls.QueryRange = append(mock.calls.QueryRange, callInfo)
mock.lockQueryRange.Unlock()
if mock.QueryRangeFunc == nil {
var (
prometheusQueryResultOut *processor.PrometheusQueryResult
errOut error
)
return prometheusQueryResultOut, errOut
}
return mock.QueryRangeFunc(ctx, q, tr)
}
// QueryRangeCalls gets all the calls that were made to QueryRange.
// Check the length with:
//
// len(mockeddatasource.Prometheus.QueryRangeCalls())
func (mock *Prometheus) QueryRangeCalls() []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
} {
var calls []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}
mock.lockQueryRange.RLock()
calls = mock.calls.QueryRange
mock.lockQueryRange.RUnlock()
return calls
lockTestConnection sync.RWMutex
}
// LabelNames calls LabelNamesFunc.
@ -266,7 +150,7 @@ func (mock *Prometheus) LabelNames(ctx context.Context, matchers []string, tr pr
// LabelNamesCalls gets all the calls that were made to LabelNames.
// Check the length with:
//
// len(mockeddatasource.Prometheus.LabelNamesCalls())
// len(mockedPrometheus.LabelNamesCalls())
func (mock *Prometheus) LabelNamesCalls() []struct {
Ctx context.Context
Matchers []string
@ -312,7 +196,7 @@ func (mock *Prometheus) LabelValues(ctx context.Context, label string, matchers
// LabelValuesCalls gets all the calls that were made to LabelValues.
// Check the length with:
//
// len(mockeddatasource.Prometheus.LabelValuesCalls())
// len(mockedPrometheus.LabelValuesCalls())
func (mock *Prometheus) LabelValuesCalls() []struct {
Ctx context.Context
Label string
@ -331,6 +215,86 @@ func (mock *Prometheus) LabelValuesCalls() []struct {
return calls
}
// Metadata calls MetadataFunc.
func (mock *Prometheus) Metadata(ctx context.Context) (*processor.PrometheusMetadataResult, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockMetadata.Lock()
mock.calls.Metadata = append(mock.calls.Metadata, callInfo)
mock.lockMetadata.Unlock()
if mock.MetadataFunc == nil {
var (
prometheusMetadataResultOut *processor.PrometheusMetadataResult
errOut error
)
return prometheusMetadataResultOut, errOut
}
return mock.MetadataFunc(ctx)
}
// MetadataCalls gets all the calls that were made to Metadata.
// Check the length with:
//
// len(mockedPrometheus.MetadataCalls())
func (mock *Prometheus) MetadataCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockMetadata.RLock()
calls = mock.calls.Metadata
mock.lockMetadata.RUnlock()
return calls
}
// QueryRange calls QueryRangeFunc.
func (mock *Prometheus) QueryRange(ctx context.Context, q string, tr processor.TimeRange) (*processor.PrometheusQueryResult, error) {
callInfo := struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}{
Ctx: ctx,
Q: q,
Tr: tr,
}
mock.lockQueryRange.Lock()
mock.calls.QueryRange = append(mock.calls.QueryRange, callInfo)
mock.lockQueryRange.Unlock()
if mock.QueryRangeFunc == nil {
var (
prometheusQueryResultOut *processor.PrometheusQueryResult
errOut error
)
return prometheusQueryResultOut, errOut
}
return mock.QueryRangeFunc(ctx, q, tr)
}
// QueryRangeCalls gets all the calls that were made to QueryRange.
// Check the length with:
//
// len(mockedPrometheus.QueryRangeCalls())
func (mock *Prometheus) QueryRangeCalls() []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
} {
var calls []struct {
Ctx context.Context
Q string
Tr processor.TimeRange
}
mock.lockQueryRange.RLock()
calls = mock.calls.QueryRange
mock.lockQueryRange.RUnlock()
return calls
}
// Series calls SeriesFunc.
func (mock *Prometheus) Series(ctx context.Context, matchers []string, tr processor.TimeRange) (*processor.PrometheusSeriesResult, error) {
callInfo := struct {
@ -358,7 +322,7 @@ func (mock *Prometheus) Series(ctx context.Context, matchers []string, tr proces
// SeriesCalls gets all the calls that were made to Series.
// Check the length with:
//
// len(mockeddatasource.Prometheus.SeriesCalls())
// len(mockedPrometheus.SeriesCalls())
func (mock *Prometheus) SeriesCalls() []struct {
Ctx context.Context
Matchers []string
@ -374,3 +338,39 @@ func (mock *Prometheus) SeriesCalls() []struct {
mock.lockSeries.RUnlock()
return calls
}
// TestConnection calls TestConnectionFunc.
func (mock *Prometheus) TestConnection(ctx context.Context) (processor.ConnectionStatus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockTestConnection.Lock()
mock.calls.TestConnection = append(mock.calls.TestConnection, callInfo)
mock.lockTestConnection.Unlock()
if mock.TestConnectionFunc == nil {
var (
connectionStatusOut processor.ConnectionStatus
errOut error
)
return connectionStatusOut, errOut
}
return mock.TestConnectionFunc(ctx)
}
// TestConnectionCalls gets all the calls that were made to TestConnection.
// Check the length with:
//
// len(mockedPrometheus.TestConnectionCalls())
func (mock *Prometheus) TestConnectionCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockTestConnection.RLock()
calls = mock.calls.TestConnection
mock.lockTestConnection.RUnlock()
return calls
}

View File

@ -17,65 +17,57 @@ var _ datasource.Runner = &Runner{}
// Runner is a mock implementation of datasource.Runner.
//
// func TestSomethingThatUsesdatasource.Runner(t *testing.T) {
// func TestSomethingThatUsesRunner(t *testing.T) {
//
// // make and configure a mocked datasource.Runner
// mockeddatasource.Runner := &Runner{
// TypeFunc: func() datasource.Type {
// panic("mock out the Type method")
// },
// TestConnectionFunc: func(ctx context.Context) (processor.ConnectionStatus, error) {
// panic("mock out the TestConnection method")
// },
// PrometheusFunc: func(ctx context.Context) (datasource.Prometheus, error) {
// panic("mock out the Prometheus method")
// mockedRunner := &Runner{
// MySQLFunc: func(ctx context.Context) (datasource.MySQL, error) {
// panic("mock out the MySQL method")
// },
// PostgreSQLFunc: func(ctx context.Context) (datasource.PostgreSQL, error) {
// panic("mock out the PostgreSQL method")
// },
// MySQLFunc: func(ctx context.Context) (datasource.MySQL, error) {
// panic("mock out the MySQL method")
// PrometheusFunc: func(ctx context.Context) (datasource.Prometheus, error) {
// panic("mock out the Prometheus method")
// },
// SQLFunc: func(ctx context.Context) (datasource.SQL, error) {
// panic("mock out the SQL method")
// },
// TestConnectionFunc: func(ctx context.Context) (processor.ConnectionStatus, error) {
// panic("mock out the TestConnection method")
// },
// TypeFunc: func() datasource.Type {
// panic("mock out the Type method")
// },
// }
//
// // use mockeddatasource.Runner in code that requires datasource.Runner
// // use mockedRunner in code that requires datasource.Runner
// // and then make assertions.
//
// }
type Runner struct {
// TypeFunc mocks the Type method.
TypeFunc func() datasource.Type
// TestConnectionFunc mocks the TestConnection method.
TestConnectionFunc func(ctx context.Context) (processor.ConnectionStatus, error)
// PrometheusFunc mocks the Prometheus method.
PrometheusFunc func(ctx context.Context) (datasource.Prometheus, error)
// MySQLFunc mocks the MySQL method.
MySQLFunc func(ctx context.Context) (datasource.MySQL, error)
// PostgreSQLFunc mocks the PostgreSQL method.
PostgreSQLFunc func(ctx context.Context) (datasource.PostgreSQL, error)
// MySQLFunc mocks the MySQL method.
MySQLFunc func(ctx context.Context) (datasource.MySQL, error)
// PrometheusFunc mocks the Prometheus method.
PrometheusFunc func(ctx context.Context) (datasource.Prometheus, error)
// SQLFunc mocks the SQL method.
SQLFunc func(ctx context.Context) (datasource.SQL, error)
// TestConnectionFunc mocks the TestConnection method.
TestConnectionFunc func(ctx context.Context) (processor.ConnectionStatus, error)
// TypeFunc mocks the Type method.
TypeFunc func() datasource.Type
// calls tracks calls to the methods.
calls struct {
// Type holds details about calls to the Type method.
Type []struct {
}
// TestConnection holds details about calls to the TestConnection method.
TestConnection []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// Prometheus holds details about calls to the Prometheus method.
Prometheus []struct {
// MySQL holds details about calls to the MySQL method.
MySQL []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
@ -84,8 +76,8 @@ type Runner struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// MySQL holds details about calls to the MySQL method.
MySQL []struct {
// Prometheus holds details about calls to the Prometheus method.
Prometheus []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
@ -94,114 +86,56 @@ type Runner struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// TestConnection holds details about calls to the TestConnection method.
TestConnection []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// Type holds details about calls to the Type method.
Type []struct {
}
}
lockType sync.RWMutex
lockTestConnection sync.RWMutex
lockPrometheus sync.RWMutex
lockPostgreSQL sync.RWMutex
lockMySQL sync.RWMutex
lockPostgreSQL sync.RWMutex
lockPrometheus sync.RWMutex
lockSQL sync.RWMutex
lockTestConnection sync.RWMutex
lockType sync.RWMutex
}
// Type calls TypeFunc.
func (mock *Runner) Type() datasource.Type {
callInfo := struct {
}{}
mock.lockType.Lock()
mock.calls.Type = append(mock.calls.Type, callInfo)
mock.lockType.Unlock()
if mock.TypeFunc == nil {
var (
typeOut datasource.Type
)
return typeOut
}
return mock.TypeFunc()
}
// TypeCalls gets all the calls that were made to Type.
// Check the length with:
//
// len(mockeddatasource.Runner.TypeCalls())
func (mock *Runner) TypeCalls() []struct {
} {
var calls []struct {
}
mock.lockType.RLock()
calls = mock.calls.Type
mock.lockType.RUnlock()
return calls
}
// TestConnection calls TestConnectionFunc.
func (mock *Runner) TestConnection(ctx context.Context) (processor.ConnectionStatus, error) {
// MySQL calls MySQLFunc.
func (mock *Runner) MySQL(ctx context.Context) (datasource.MySQL, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockTestConnection.Lock()
mock.calls.TestConnection = append(mock.calls.TestConnection, callInfo)
mock.lockTestConnection.Unlock()
if mock.TestConnectionFunc == nil {
mock.lockMySQL.Lock()
mock.calls.MySQL = append(mock.calls.MySQL, callInfo)
mock.lockMySQL.Unlock()
if mock.MySQLFunc == nil {
var (
connectionStatusOut processor.ConnectionStatus
errOut error
mySQLOut datasource.MySQL
errOut error
)
return connectionStatusOut, errOut
return mySQLOut, errOut
}
return mock.TestConnectionFunc(ctx)
return mock.MySQLFunc(ctx)
}
// TestConnectionCalls gets all the calls that were made to TestConnection.
// MySQLCalls gets all the calls that were made to MySQL.
// Check the length with:
//
// len(mockeddatasource.Runner.TestConnectionCalls())
func (mock *Runner) TestConnectionCalls() []struct {
// len(mockedRunner.MySQLCalls())
func (mock *Runner) MySQLCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockTestConnection.RLock()
calls = mock.calls.TestConnection
mock.lockTestConnection.RUnlock()
return calls
}
// Prometheus calls PrometheusFunc.
func (mock *Runner) Prometheus(ctx context.Context) (datasource.Prometheus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockPrometheus.Lock()
mock.calls.Prometheus = append(mock.calls.Prometheus, callInfo)
mock.lockPrometheus.Unlock()
if mock.PrometheusFunc == nil {
var (
prometheusOut datasource.Prometheus
errOut error
)
return prometheusOut, errOut
}
return mock.PrometheusFunc(ctx)
}
// PrometheusCalls gets all the calls that were made to Prometheus.
// Check the length with:
//
// len(mockeddatasource.Runner.PrometheusCalls())
func (mock *Runner) PrometheusCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockPrometheus.RLock()
calls = mock.calls.Prometheus
mock.lockPrometheus.RUnlock()
mock.lockMySQL.RLock()
calls = mock.calls.MySQL
mock.lockMySQL.RUnlock()
return calls
}
@ -228,7 +162,7 @@ func (mock *Runner) PostgreSQL(ctx context.Context) (datasource.PostgreSQL, erro
// PostgreSQLCalls gets all the calls that were made to PostgreSQL.
// Check the length with:
//
// len(mockeddatasource.Runner.PostgreSQLCalls())
// len(mockedRunner.PostgreSQLCalls())
func (mock *Runner) PostgreSQLCalls() []struct {
Ctx context.Context
} {
@ -241,39 +175,39 @@ func (mock *Runner) PostgreSQLCalls() []struct {
return calls
}
// MySQL calls MySQLFunc.
func (mock *Runner) MySQL(ctx context.Context) (datasource.MySQL, error) {
// Prometheus calls PrometheusFunc.
func (mock *Runner) Prometheus(ctx context.Context) (datasource.Prometheus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockMySQL.Lock()
mock.calls.MySQL = append(mock.calls.MySQL, callInfo)
mock.lockMySQL.Unlock()
if mock.MySQLFunc == nil {
mock.lockPrometheus.Lock()
mock.calls.Prometheus = append(mock.calls.Prometheus, callInfo)
mock.lockPrometheus.Unlock()
if mock.PrometheusFunc == nil {
var (
mySQLOut datasource.MySQL
errOut error
prometheusOut datasource.Prometheus
errOut error
)
return mySQLOut, errOut
return prometheusOut, errOut
}
return mock.MySQLFunc(ctx)
return mock.PrometheusFunc(ctx)
}
// MySQLCalls gets all the calls that were made to MySQL.
// PrometheusCalls gets all the calls that were made to Prometheus.
// Check the length with:
//
// len(mockeddatasource.Runner.MySQLCalls())
func (mock *Runner) MySQLCalls() []struct {
// len(mockedRunner.PrometheusCalls())
func (mock *Runner) PrometheusCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockMySQL.RLock()
calls = mock.calls.MySQL
mock.lockMySQL.RUnlock()
mock.lockPrometheus.RLock()
calls = mock.calls.Prometheus
mock.lockPrometheus.RUnlock()
return calls
}
@ -300,7 +234,7 @@ func (mock *Runner) SQL(ctx context.Context) (datasource.SQL, error) {
// SQLCalls gets all the calls that were made to SQL.
// Check the length with:
//
// len(mockeddatasource.Runner.SQLCalls())
// len(mockedRunner.SQLCalls())
func (mock *Runner) SQLCalls() []struct {
Ctx context.Context
} {
@ -312,3 +246,69 @@ func (mock *Runner) SQLCalls() []struct {
mock.lockSQL.RUnlock()
return calls
}
// TestConnection calls TestConnectionFunc.
func (mock *Runner) TestConnection(ctx context.Context) (processor.ConnectionStatus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockTestConnection.Lock()
mock.calls.TestConnection = append(mock.calls.TestConnection, callInfo)
mock.lockTestConnection.Unlock()
if mock.TestConnectionFunc == nil {
var (
connectionStatusOut processor.ConnectionStatus
errOut error
)
return connectionStatusOut, errOut
}
return mock.TestConnectionFunc(ctx)
}
// TestConnectionCalls gets all the calls that were made to TestConnection.
// Check the length with:
//
// len(mockedRunner.TestConnectionCalls())
func (mock *Runner) TestConnectionCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockTestConnection.RLock()
calls = mock.calls.TestConnection
mock.lockTestConnection.RUnlock()
return calls
}
// Type calls TypeFunc.
func (mock *Runner) Type() datasource.Type {
callInfo := struct {
}{}
mock.lockType.Lock()
mock.calls.Type = append(mock.calls.Type, callInfo)
mock.lockType.Unlock()
if mock.TypeFunc == nil {
var (
typeOut datasource.Type
)
return typeOut
}
return mock.TypeFunc()
}
// TypeCalls gets all the calls that were made to Type.
// Check the length with:
//
// len(mockedRunner.TypeCalls())
func (mock *Runner) TypeCalls() []struct {
} {
var calls []struct {
}
mock.lockType.RLock()
calls = mock.calls.Type
mock.lockType.RUnlock()
return calls
}

View File

@ -17,42 +17,37 @@ var _ datasource.SQL = &SQL{}
// SQL is a mock implementation of datasource.SQL.
//
// func TestSomethingThatUsesdatasource.SQL(t *testing.T) {
// func TestSomethingThatUsesSQL(t *testing.T) {
//
// // make and configure a mocked datasource.SQL
// mockeddatasource.SQL := &SQL{
// TestConnectionFunc: func(ctx context.Context) (processor.ConnectionStatus, error) {
// panic("mock out the TestConnection method")
// },
// mockedSQL := &SQL{
// MetadataFunc: func(ctx context.Context) (*processor.SQLMetadataResult, error) {
// panic("mock out the Metadata method")
// },
// QueryLabelsFunc: func(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error) {
// panic("mock out the QueryLabels method")
// },
// TestConnectionFunc: func(ctx context.Context) (processor.ConnectionStatus, error) {
// panic("mock out the TestConnection method")
// },
// }
//
// // use mockeddatasource.SQL in code that requires datasource.SQL
// // use mockedSQL in code that requires datasource.SQL
// // and then make assertions.
//
// }
type SQL struct {
// TestConnectionFunc mocks the TestConnection method.
TestConnectionFunc func(ctx context.Context) (processor.ConnectionStatus, error)
// MetadataFunc mocks the Metadata method.
MetadataFunc func(ctx context.Context) (*processor.SQLMetadataResult, error)
// QueryLabelsFunc mocks the QueryLabels method.
QueryLabelsFunc func(ctx context.Context, q string, tr processor.TimeRange) (map[string]string, error)
// TestConnectionFunc mocks the TestConnection method.
TestConnectionFunc func(ctx context.Context) (processor.ConnectionStatus, error)
// calls tracks calls to the methods.
calls struct {
// TestConnection holds details about calls to the TestConnection method.
TestConnection []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
// Metadata holds details about calls to the Metadata method.
Metadata []struct {
// Ctx is the ctx argument value.
@ -67,46 +62,15 @@ type SQL struct {
// Tr is the tr argument value.
Tr processor.TimeRange
}
// TestConnection holds details about calls to the TestConnection method.
TestConnection []struct {
// Ctx is the ctx argument value.
Ctx context.Context
}
}
lockTestConnection sync.RWMutex
lockMetadata sync.RWMutex
lockQueryLabels sync.RWMutex
}
// TestConnection calls TestConnectionFunc.
func (mock *SQL) TestConnection(ctx context.Context) (processor.ConnectionStatus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockTestConnection.Lock()
mock.calls.TestConnection = append(mock.calls.TestConnection, callInfo)
mock.lockTestConnection.Unlock()
if mock.TestConnectionFunc == nil {
var (
connectionStatusOut processor.ConnectionStatus
errOut error
)
return connectionStatusOut, errOut
}
return mock.TestConnectionFunc(ctx)
}
// TestConnectionCalls gets all the calls that were made to TestConnection.
// Check the length with:
//
// len(mockeddatasource.SQL.TestConnectionCalls())
func (mock *SQL) TestConnectionCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockTestConnection.RLock()
calls = mock.calls.TestConnection
mock.lockTestConnection.RUnlock()
return calls
lockTestConnection sync.RWMutex
}
// Metadata calls MetadataFunc.
@ -132,7 +96,7 @@ func (mock *SQL) Metadata(ctx context.Context) (*processor.SQLMetadataResult, er
// MetadataCalls gets all the calls that were made to Metadata.
// Check the length with:
//
// len(mockeddatasource.SQL.MetadataCalls())
// len(mockedSQL.MetadataCalls())
func (mock *SQL) MetadataCalls() []struct {
Ctx context.Context
} {
@ -172,7 +136,7 @@ func (mock *SQL) QueryLabels(ctx context.Context, q string, tr processor.TimeRan
// QueryLabelsCalls gets all the calls that were made to QueryLabels.
// Check the length with:
//
// len(mockeddatasource.SQL.QueryLabelsCalls())
// len(mockedSQL.QueryLabelsCalls())
func (mock *SQL) QueryLabelsCalls() []struct {
Ctx context.Context
Q string
@ -188,3 +152,39 @@ func (mock *SQL) QueryLabelsCalls() []struct {
mock.lockQueryLabels.RUnlock()
return calls
}
// TestConnection calls TestConnectionFunc.
func (mock *SQL) TestConnection(ctx context.Context) (processor.ConnectionStatus, error) {
callInfo := struct {
Ctx context.Context
}{
Ctx: ctx,
}
mock.lockTestConnection.Lock()
mock.calls.TestConnection = append(mock.calls.TestConnection, callInfo)
mock.lockTestConnection.Unlock()
if mock.TestConnectionFunc == nil {
var (
connectionStatusOut processor.ConnectionStatus
errOut error
)
return connectionStatusOut, errOut
}
return mock.TestConnectionFunc(ctx)
}
// TestConnectionCalls gets all the calls that were made to TestConnection.
// Check the length with:
//
// len(mockedSQL.TestConnectionCalls())
func (mock *SQL) TestConnectionCalls() []struct {
Ctx context.Context
} {
var calls []struct {
Ctx context.Context
}
mock.lockTestConnection.RLock()
calls = mock.calls.TestConnection
mock.lockTestConnection.RUnlock()
return calls
}

View File

@ -18,16 +18,16 @@ var _ datasource.StatusStore = &StatusStore{}
// StatusStore is a mock implementation of datasource.StatusStore.
//
// func TestSomethingThatUsesdatasource.StatusStore(t *testing.T) {
// func TestSomethingThatUsesStatusStore(t *testing.T) {
//
// // make and configure a mocked datasource.StatusStore
// mockeddatasource.StatusStore := &StatusStore{
// mockedStatusStore := &StatusStore{
// UpdateDataSourceStatusFunc: func(ctx context.Context, id xid.ID, organizationID string, status processor.ConnectionStatus) error {
// panic("mock out the UpdateDataSourceStatus method")
// },
// }
//
// // use mockeddatasource.StatusStore in code that requires datasource.StatusStore
// // use mockedStatusStore in code that requires datasource.StatusStore
// // and then make assertions.
//
// }
@ -41,8 +41,8 @@ type StatusStore struct {
UpdateDataSourceStatus []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// Id is the id argument value.
Id xid.ID
// ID is the id argument value.
ID xid.ID
// OrganizationID is the organizationID argument value.
OrganizationID string
// Status is the status argument value.
@ -56,12 +56,12 @@ type StatusStore struct {
func (mock *StatusStore) UpdateDataSourceStatus(ctx context.Context, id xid.ID, organizationID string, status processor.ConnectionStatus) error {
callInfo := struct {
Ctx context.Context
Id xid.ID
ID xid.ID
OrganizationID string
Status processor.ConnectionStatus
}{
Ctx: ctx,
Id: id,
ID: id,
OrganizationID: organizationID,
Status: status,
}
@ -80,16 +80,16 @@ func (mock *StatusStore) UpdateDataSourceStatus(ctx context.Context, id xid.ID,
// UpdateDataSourceStatusCalls gets all the calls that were made to UpdateDataSourceStatus.
// Check the length with:
//
// len(mockeddatasource.StatusStore.UpdateDataSourceStatusCalls())
// len(mockedStatusStore.UpdateDataSourceStatusCalls())
func (mock *StatusStore) UpdateDataSourceStatusCalls() []struct {
Ctx context.Context
Id xid.ID
ID xid.ID
OrganizationID string
Status processor.ConnectionStatus
} {
var calls []struct {
Ctx context.Context
Id xid.ID
ID xid.ID
OrganizationID string
Status processor.ConnectionStatus
}

View File

@ -287,7 +287,7 @@ type Prometheus interface {
// SQL represents a SQL-based data source processor.
//
//go:generate ../../scripts/codegen/mock SQL
//go:generate ../../scripts/codegen/mock SQL sql
type SQL interface {
connectionTester
@ -300,7 +300,7 @@ type SQL interface {
// PostgreSQL represents a PostgreSQL data source processor.
//
//go:generate ../../scripts/codegen/mock PostgreSQL
//go:generate ../../scripts/codegen/mock PostgreSQL postgresql
type PostgreSQL interface {
SQL
@ -310,7 +310,7 @@ type PostgreSQL interface {
// MySQL represents a MySQL data source processor.
//
//go:generate ../../scripts/codegen/mock MySQL
//go:generate ../../scripts/codegen/mock MySQL mysql
type MySQL interface {
SQL

View File

@ -344,7 +344,7 @@ func Test_connect(t *testing.T) {
ff := store.UpdateDataSourceStatusCalls()
require.Len(t, ff, 1)
assert.Equal(t, r.ds.ID, ff[0].Id)
assert.Equal(t, r.ds.ID, ff[0].ID)
assert.Equal(t, "org", ff[0].OrganizationID)
assert.Equal(t, processor.ConnectionStatusSuccess, ff[0].Status)

View File

@ -30,8 +30,8 @@ var ErrNotSimulated = errutil.New(
// Result is what running a simulated metric block reports back.
type Result struct {
// Cleared reports whether the block's real data has arrived and the
// simulation has been taken off it.
// Cleared indicates whether the block's real data has arrived and
// the simulation has been switched off.
Cleared bool `json:"cleared"`
}
@ -100,8 +100,8 @@ var _probeSpans = map[string]time.Duration{
// rows.
const _queryQuery = "query"
// Checker probes the data source of a simulated metric block and takes
// the simulation off the block once real data answers.
// Checker probes the data source of a simulated metric block and
// switches the simulation off once real data answers.
type Checker struct {
// log is the component logger.
log *slog.Logger
@ -112,7 +112,7 @@ type Checker struct {
// runners hands out the client that speaks to a data source.
runners Runners
// applier writes the cleared attribute onto the live document.
// applier writes the switched-off flag onto the live document.
applier Applier
// results remembers each block's last verdict for _resultTTL.
@ -141,7 +141,7 @@ func (c *Checker) Start(ctx context.Context) {
c.results.Start(ctx)
}
// Check reports whether the simulation was taken off the block, probing
// Check reports whether the block's simulation was switched off, probing
// the block's data source when no recent verdict is remembered. A data
// source that cannot be reached, or that answers with nothing, leaves the
// block simulating and is not an error: it is the state the simulation
@ -152,7 +152,7 @@ func (c *Checker) Check(
block document.Block,
organizationID string,
) (Result, error) {
if _, simulated := block.Attrs.Value(document.AttrSimulationPreset); !simulated {
if !simulated(block) {
return Result{}, ErrNotSimulated
}
@ -304,7 +304,8 @@ func (c *Checker) query(
}
}
// clear takes the simulation off the block on the live document.
// clear switches the block's simulation off on the live document. The
// preset stays, so the block remembers which series it drew.
func (c *Checker) clear(
ctx context.Context,
documentID, branchID xid.ID,
@ -312,7 +313,7 @@ func (c *Checker) clear(
) error {
res, err := c.applier.Apply(ctx, documentID, branchID, []edit.Operation{
edit.UpdateAttrs(blockUID, map[string]any{
document.AttrSimulationPreset: nil,
document.AttrSimulationActive: false,
}),
}, true)
if err != nil {
@ -342,6 +343,21 @@ func cacheKey(documentID, branchID xid.ID, blockUID string) string {
return documentID.String() + "/" + branchID.String() + "/" + blockUID
}
// simulated reports whether the block draws generated data: it names a
// preset and its simulation is switched on.
func simulated(block document.Block) bool {
if _, ok := block.Attrs.Value(document.AttrSimulationPreset); !ok {
return false
}
active, ok := block.Attrs.Get(document.AttrSimulationActive)
if !ok {
return false
}
return active.Bool()
}
// probeSpan reads how far back the block's window reaches.
func probeSpan(block document.Block) time.Duration {
raw, ok := block.Attrs.Value(document.AttrTimeRange)

View File

@ -45,6 +45,7 @@ func metricAttrs() document.Attributes {
map[string]any{"name": "Query 1", "query": "up", "legendFormat": ""},
},
document.AttrSimulationPreset: "cpu_usage",
document.AttrSimulationActive: true,
}
}
@ -368,11 +369,26 @@ func Test_Checker_Check(t *testing.T) {
}),
Prometheus: sampled(),
},
"The block is not simulating": {
"The block names no preset": {
Attrs: withoutAttr(document.AttrSimulationPreset),
Prometheus: sampled(),
Err: ErrNotSimulated,
},
"The simulation is switched off": {
Attrs: withAttr(document.AttrSimulationActive, false),
Prometheus: sampled(),
Err: ErrNotSimulated,
},
"The block carries a preset and no flag": {
Attrs: withoutAttr(document.AttrSimulationActive),
Prometheus: sampled(),
Err: ErrNotSimulated,
},
"The flag is not a boolean": {
Attrs: withAttr(document.AttrSimulationActive, "true"),
Prometheus: sampled(),
Err: ErrNotSimulated,
},
"The block carries no uid": {
Attrs: withoutAttr(document.AttrUID),
Prometheus: sampled(),
@ -499,7 +515,7 @@ func Test_Checker_Check(t *testing.T) {
assert.JSONEq(
t,
`{"kind":"update_attrs","block_uid":"block-1","attrs":{"simulationPreset":null}}`,
`{"kind":"update_attrs","block_uid":"block-1","attrs":{"simulationActive":false}}`,
string(raw),
)
}

View File

@ -87,9 +87,15 @@ const (
AttrAxisBoundsMax = "axisBoundsMax"
// AttrSimulationPreset names the generated series a metric block
// draws while its data source has no real data to answer with. It
// is unset on a block that queries normally.
// draws while AttrSimulationActive is set. It is unset on a block
// nobody has simulated. Once set, it stays after the simulation ends.
AttrSimulationPreset = "simulationPreset"
// AttrSimulationActive is true while a metric block draws its
// AttrSimulationPreset because its data source has no real data yet.
// Only the editor and core use it. Tools and document readers never
// see it.
AttrSimulationActive = "simulationActive"
)
// MarkComment is the inline mark type anchoring a comment to a range of

View File

@ -5,10 +5,11 @@ package manager
import (
"context"
"sync"
"github.com/oxynote/oxynote/server/core/internal/document"
"github.com/oxynote/oxynote/server/core/internal/search"
"github.com/rs/xid"
"sync"
)
// Ensure, that DBMock does implement DB.
@ -150,10 +151,10 @@ func (mock *DBMock) FetchDocumentBranchesUnsafe(ctx context.Context, docID xid.I
mock.lockFetchDocumentBranchesUnsafe.Unlock()
if mock.FetchDocumentBranchesUnsafeFunc == nil {
var (
out0 []document.BranchSummary
errOut error
branchSummarysOut []document.BranchSummary
errOut error
)
return out0, errOut
return branchSummarysOut, errOut
}
return mock.FetchDocumentBranchesUnsafeFunc(ctx, docID)
}
@ -190,10 +191,10 @@ func (mock *DBMock) FetchDocumentUnsafeByBranchID(ctx context.Context, branchID
mock.lockFetchDocumentUnsafeByBranchID.Unlock()
if mock.FetchDocumentUnsafeByBranchIDFunc == nil {
var (
out0 *document.Document
errOut error
documentOut *document.Document
errOut error
)
return out0, errOut
return documentOut, errOut
}
return mock.FetchDocumentUnsafeByBranchIDFunc(ctx, branchID)
}
@ -232,10 +233,10 @@ func (mock *DBMock) FetchSearchJobs(ctx context.Context, offsetID int64, limit i
mock.lockFetchSearchJobs.Unlock()
if mock.FetchSearchJobsFunc == nil {
var (
out0 []search.Job
errOut error
jobsOut []search.Job
errOut error
)
return out0, errOut
return jobsOut, errOut
}
return mock.FetchSearchJobsFunc(ctx, offsetID, limit)
}

View File

@ -5,9 +5,10 @@ package manager
import (
"context"
"sync"
"github.com/oxynote/oxynote/server/core/internal/search"
"github.com/rs/xid"
"sync"
)
// Ensure, that IndexMock does implement Index.

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ var _ SearchTrigger = &SearchTriggerMock{}
//
// // make and configure a mocked SearchTrigger
// mockedSearchTrigger := &SearchTriggerMock{
// TriggerFunc: func() {
// TriggerFunc: func() {
// panic("mock out the Trigger method")
// },
// }

View File

@ -23,6 +23,9 @@ var _ Manager = &ManagerMock{}
// ChatFunc: func(ctx context.Context, orgID string, userID string, conn protocol.SessionConn) error {
// panic("mock out the Chat method")
// },
// ConfiguredFunc: func() bool {
// panic("mock out the Configured method")
// },
// }
//
// // use mockedManager in code that requires Manager
@ -30,17 +33,14 @@ var _ Manager = &ManagerMock{}
//
// }
type ManagerMock struct {
// ConfiguredFunc mocks the Configured method.
ConfiguredFunc func() bool
// ChatFunc mocks the Chat method.
ChatFunc func(ctx context.Context, orgID string, userID string, conn protocol.SessionConn) error
// ConfiguredFunc mocks the Configured method.
ConfiguredFunc func() bool
// calls tracks calls to the methods.
calls struct {
// Configured holds details about calls to the Configured method.
Configured []struct {
}
// Chat holds details about calls to the Chat method.
Chat []struct {
// Ctx is the ctx argument value.
@ -52,9 +52,12 @@ type ManagerMock struct {
// Conn is the conn argument value.
Conn protocol.SessionConn
}
// Configured holds details about calls to the Configured method.
Configured []struct {
}
}
lockConfigured sync.RWMutex
lockChat sync.RWMutex
lockConfigured sync.RWMutex
}
// Chat calls ChatFunc.

View File

@ -72,10 +72,10 @@ func (mock *RunnerMock) Run(ctx context.Context, documentID xid.ID, branchID xid
mock.lockRun.Unlock()
if mock.RunFunc == nil {
var (
ifaceValOut any
errOut error
vOut any
errOut error
)
return ifaceValOut, errOut
return vOut, errOut
}
return mock.RunFunc(ctx, documentID, branchID, blockUID, organizationID)
}

View File

@ -55,6 +55,9 @@ var _ DB = &DBMock{}
// DeleteDocumentCommentsByBranchIDFunc: func(ctx context.Context, branchID xid.ID, organizationID string) error {
// panic("mock out the DeleteDocumentCommentsByBranchID method")
// },
// DetachDocumentHooksByBranchIDFunc: func(ctx context.Context, branchID xid.ID, organizationID string) error {
// panic("mock out the DetachDocumentHooksByBranchID method")
// },
// FetchBranchReviewerFunc: func(ctx context.Context, branchID xid.ID, userID string, organizationID string) (*documentCore.BranchReviewer, error) {
// panic("mock out the FetchBranchReviewer method")
// },
@ -118,9 +121,6 @@ var _ DB = &DBMock{}
// ReplaceBranchTagsFunc: func(ctx context.Context, organizationID string, fromBranchID xid.ID, toBranchID xid.ID) error {
// panic("mock out the ReplaceBranchTags method")
// },
// DetachDocumentHooksByBranchIDFunc: func(ctx context.Context, branchID xid.ID, organizationID string) error {
// panic("mock out the DetachDocumentHooksByBranchID method")
// },
// UpdateBranchReviewerFunc: func(ctx context.Context, reviewer documentCore.BranchReviewer) error {
// panic("mock out the UpdateBranchReviewer method")
// },
@ -176,6 +176,9 @@ type DBMock struct {
// DeleteDocumentCommentsByBranchIDFunc mocks the DeleteDocumentCommentsByBranchID method.
DeleteDocumentCommentsByBranchIDFunc func(ctx context.Context, branchID xid.ID, organizationID string) error
// DetachDocumentHooksByBranchIDFunc mocks the DetachDocumentHooksByBranchID method.
DetachDocumentHooksByBranchIDFunc func(ctx context.Context, branchID xid.ID, organizationID string) error
// FetchBranchReviewerFunc mocks the FetchBranchReviewer method.
FetchBranchReviewerFunc func(ctx context.Context, branchID xid.ID, userID string, organizationID string) (*documentCore.BranchReviewer, error)
@ -239,9 +242,6 @@ type DBMock struct {
// ReplaceBranchTagsFunc mocks the ReplaceBranchTags method.
ReplaceBranchTagsFunc func(ctx context.Context, organizationID string, fromBranchID xid.ID, toBranchID xid.ID) error
// DetachDocumentHooksByBranchIDFunc mocks the DetachDocumentHooksByBranchID method.
DetachDocumentHooksByBranchIDFunc func(ctx context.Context, branchID xid.ID, organizationID string) error
// UpdateBranchReviewerFunc mocks the UpdateBranchReviewer method.
UpdateBranchReviewerFunc func(ctx context.Context, reviewer documentCore.BranchReviewer) error
@ -356,6 +356,15 @@ type DBMock struct {
// OrganizationID is the organizationID argument value.
OrganizationID string
}
// DetachDocumentHooksByBranchID holds details about calls to the DetachDocumentHooksByBranchID method.
DetachDocumentHooksByBranchID []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// BranchID is the branchID argument value.
BranchID xid.ID
// OrganizationID is the organizationID argument value.
OrganizationID string
}
// FetchBranchReviewer holds details about calls to the FetchBranchReviewer method.
FetchBranchReviewer []struct {
// Ctx is the ctx argument value.
@ -535,15 +544,6 @@ type DBMock struct {
// ToBranchID is the toBranchID argument value.
ToBranchID xid.ID
}
// DetachDocumentHooksByBranchID holds details about calls to the DetachDocumentHooksByBranchID method.
DetachDocumentHooksByBranchID []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// BranchID is the branchID argument value.
BranchID xid.ID
// OrganizationID is the organizationID argument value.
OrganizationID string
}
// UpdateBranchReviewer holds details about calls to the UpdateBranchReviewer method.
UpdateBranchReviewer []struct {
// Ctx is the ctx argument value.
@ -607,6 +607,7 @@ type DBMock struct {
lockDeleteDocument sync.RWMutex
lockDeleteDocumentBranchByID sync.RWMutex
lockDeleteDocumentCommentsByBranchID sync.RWMutex
lockDetachDocumentHooksByBranchID sync.RWMutex
lockFetchBranchReviewer sync.RWMutex
lockFetchBranchReviewers sync.RWMutex
lockFetchDocument sync.RWMutex
@ -628,7 +629,6 @@ type DBMock struct {
lockInsertSearchJob sync.RWMutex
lockPromoteBranchApprovals sync.RWMutex
lockReplaceBranchTags sync.RWMutex
lockDetachDocumentHooksByBranchID sync.RWMutex
lockUpdateBranchReviewer sync.RWMutex
lockUpdateDocument sync.RWMutex
lockUpdateDocumentBranchMetadata sync.RWMutex
@ -828,7 +828,9 @@ func (mock *DBMock) CopyBranchTags(ctx context.Context, organizationID string, f
mock.calls.CopyBranchTags = append(mock.calls.CopyBranchTags, callInfo)
mock.lockCopyBranchTags.Unlock()
if mock.CopyBranchTagsFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.CopyBranchTagsFunc(ctx, organizationID, fromBranchID, toBranchID)
@ -1077,6 +1079,49 @@ func (mock *DBMock) DeleteDocumentCommentsByBranchIDCalls() []struct {
return calls
}
// DetachDocumentHooksByBranchID calls DetachDocumentHooksByBranchIDFunc.
func (mock *DBMock) DetachDocumentHooksByBranchID(ctx context.Context, branchID xid.ID, organizationID string) error {
callInfo := struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
}{
Ctx: ctx,
BranchID: branchID,
OrganizationID: organizationID,
}
mock.lockDetachDocumentHooksByBranchID.Lock()
mock.calls.DetachDocumentHooksByBranchID = append(mock.calls.DetachDocumentHooksByBranchID, callInfo)
mock.lockDetachDocumentHooksByBranchID.Unlock()
if mock.DetachDocumentHooksByBranchIDFunc == nil {
var (
errOut error
)
return errOut
}
return mock.DetachDocumentHooksByBranchIDFunc(ctx, branchID, organizationID)
}
// DetachDocumentHooksByBranchIDCalls gets all the calls that were made to DetachDocumentHooksByBranchID.
// Check the length with:
//
// len(mockedDB.DetachDocumentHooksByBranchIDCalls())
func (mock *DBMock) DetachDocumentHooksByBranchIDCalls() []struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
} {
var calls []struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
}
mock.lockDetachDocumentHooksByBranchID.RLock()
calls = mock.calls.DetachDocumentHooksByBranchID
mock.lockDetachDocumentHooksByBranchID.RUnlock()
return calls
}
// FetchBranchReviewer calls FetchBranchReviewerFunc.
func (mock *DBMock) FetchBranchReviewer(ctx context.Context, branchID xid.ID, userID string, organizationID string) (*documentCore.BranchReviewer, error) {
callInfo := struct {
@ -1943,7 +1988,9 @@ func (mock *DBMock) ReplaceBranchTags(ctx context.Context, organizationID string
mock.calls.ReplaceBranchTags = append(mock.calls.ReplaceBranchTags, callInfo)
mock.lockReplaceBranchTags.Unlock()
if mock.ReplaceBranchTagsFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.ReplaceBranchTagsFunc(ctx, organizationID, fromBranchID, toBranchID)
@ -1971,49 +2018,6 @@ func (mock *DBMock) ReplaceBranchTagsCalls() []struct {
return calls
}
// DetachDocumentHooksByBranchID calls DetachDocumentHooksByBranchIDFunc.
func (mock *DBMock) DetachDocumentHooksByBranchID(ctx context.Context, branchID xid.ID, organizationID string) error {
callInfo := struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
}{
Ctx: ctx,
BranchID: branchID,
OrganizationID: organizationID,
}
mock.lockDetachDocumentHooksByBranchID.Lock()
mock.calls.DetachDocumentHooksByBranchID = append(mock.calls.DetachDocumentHooksByBranchID, callInfo)
mock.lockDetachDocumentHooksByBranchID.Unlock()
if mock.DetachDocumentHooksByBranchIDFunc == nil {
var (
errOut error
)
return errOut
}
return mock.DetachDocumentHooksByBranchIDFunc(ctx, branchID, organizationID)
}
// DetachDocumentHooksByBranchIDCalls gets all the calls that were made to DetachDocumentHooksByBranchID.
// Check the length with:
//
// len(mockedDB.DetachDocumentHooksByBranchIDCalls())
func (mock *DBMock) DetachDocumentHooksByBranchIDCalls() []struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
} {
var calls []struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
}
mock.lockDetachDocumentHooksByBranchID.RLock()
calls = mock.calls.DetachDocumentHooksByBranchID
mock.lockDetachDocumentHooksByBranchID.RUnlock()
return calls
}
// UpdateBranchReviewer calls UpdateBranchReviewerFunc.
func (mock *DBMock) UpdateBranchReviewer(ctx context.Context, reviewer documentCore.BranchReviewer) error {
callInfo := struct {

View File

@ -17,7 +17,7 @@ var _ SearchTrigger = &SearchTriggerMock{}
//
// // make and configure a mocked SearchTrigger
// mockedSearchTrigger := &SearchTriggerMock{
// TriggerFunc: func() {
// TriggerFunc: func() {
// panic("mock out the Trigger method")
// },
// }

View File

@ -62,10 +62,10 @@ func (mock *SearcherMock) SearchDocuments(ctx context.Context, organizationID st
mock.lockSearchDocuments.Unlock()
if mock.SearchDocumentsFunc == nil {
var (
out0 []byte
errOut error
bytesOut []byte
errOut error
)
return out0, errOut
return bytesOut, errOut
}
return mock.SearchDocumentsFunc(ctx, organizationID, query)
}

View File

@ -55,6 +55,9 @@ var _ Tx = &TxMock{}
// DeleteDocumentCommentsByBranchIDFunc: func(ctx context.Context, branchID xid.ID, organizationID string) error {
// panic("mock out the DeleteDocumentCommentsByBranchID method")
// },
// DetachDocumentHooksByBranchIDFunc: func(ctx context.Context, branchID xid.ID, organizationID string) error {
// panic("mock out the DetachDocumentHooksByBranchID method")
// },
// FetchBranchReviewerFunc: func(ctx context.Context, branchID xid.ID, userID string, organizationID string) (*documentCore.BranchReviewer, error) {
// panic("mock out the FetchBranchReviewer method")
// },
@ -121,9 +124,6 @@ var _ Tx = &TxMock{}
// RollbackFunc: func() error {
// panic("mock out the Rollback method")
// },
// DetachDocumentHooksByBranchIDFunc: func(ctx context.Context, branchID xid.ID, organizationID string) error {
// panic("mock out the DetachDocumentHooksByBranchID method")
// },
// UpdateBranchReviewerFunc: func(ctx context.Context, reviewer documentCore.BranchReviewer) error {
// panic("mock out the UpdateBranchReviewer method")
// },
@ -179,6 +179,9 @@ type TxMock struct {
// DeleteDocumentCommentsByBranchIDFunc mocks the DeleteDocumentCommentsByBranchID method.
DeleteDocumentCommentsByBranchIDFunc func(ctx context.Context, branchID xid.ID, organizationID string) error
// DetachDocumentHooksByBranchIDFunc mocks the DetachDocumentHooksByBranchID method.
DetachDocumentHooksByBranchIDFunc func(ctx context.Context, branchID xid.ID, organizationID string) error
// FetchBranchReviewerFunc mocks the FetchBranchReviewer method.
FetchBranchReviewerFunc func(ctx context.Context, branchID xid.ID, userID string, organizationID string) (*documentCore.BranchReviewer, error)
@ -245,9 +248,6 @@ type TxMock struct {
// RollbackFunc mocks the Rollback method.
RollbackFunc func() error
// DetachDocumentHooksByBranchIDFunc mocks the DetachDocumentHooksByBranchID method.
DetachDocumentHooksByBranchIDFunc func(ctx context.Context, branchID xid.ID, organizationID string) error
// UpdateBranchReviewerFunc mocks the UpdateBranchReviewer method.
UpdateBranchReviewerFunc func(ctx context.Context, reviewer documentCore.BranchReviewer) error
@ -358,6 +358,15 @@ type TxMock struct {
// OrganizationID is the organizationID argument value.
OrganizationID string
}
// DetachDocumentHooksByBranchID holds details about calls to the DetachDocumentHooksByBranchID method.
DetachDocumentHooksByBranchID []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// BranchID is the branchID argument value.
BranchID xid.ID
// OrganizationID is the organizationID argument value.
OrganizationID string
}
// FetchBranchReviewer holds details about calls to the FetchBranchReviewer method.
FetchBranchReviewer []struct {
// Ctx is the ctx argument value.
@ -540,15 +549,6 @@ type TxMock struct {
// Rollback holds details about calls to the Rollback method.
Rollback []struct {
}
// DetachDocumentHooksByBranchID holds details about calls to the DetachDocumentHooksByBranchID method.
DetachDocumentHooksByBranchID []struct {
// Ctx is the ctx argument value.
Ctx context.Context
// BranchID is the branchID argument value.
BranchID xid.ID
// OrganizationID is the organizationID argument value.
OrganizationID string
}
// UpdateBranchReviewer holds details about calls to the UpdateBranchReviewer method.
UpdateBranchReviewer []struct {
// Ctx is the ctx argument value.
@ -612,6 +612,7 @@ type TxMock struct {
lockDeleteDocument sync.RWMutex
lockDeleteDocumentBranchByID sync.RWMutex
lockDeleteDocumentCommentsByBranchID sync.RWMutex
lockDetachDocumentHooksByBranchID sync.RWMutex
lockFetchBranchReviewer sync.RWMutex
lockFetchBranchReviewers sync.RWMutex
lockFetchDocument sync.RWMutex
@ -634,7 +635,6 @@ type TxMock struct {
lockPromoteBranchApprovals sync.RWMutex
lockReplaceBranchTags sync.RWMutex
lockRollback sync.RWMutex
lockDetachDocumentHooksByBranchID sync.RWMutex
lockUpdateBranchReviewer sync.RWMutex
lockUpdateDocument sync.RWMutex
lockUpdateDocumentBranchMetadata sync.RWMutex
@ -825,7 +825,9 @@ func (mock *TxMock) CopyBranchTags(ctx context.Context, organizationID string, f
mock.calls.CopyBranchTags = append(mock.calls.CopyBranchTags, callInfo)
mock.lockCopyBranchTags.Unlock()
if mock.CopyBranchTagsFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.CopyBranchTagsFunc(ctx, organizationID, fromBranchID, toBranchID)
@ -1074,6 +1076,49 @@ func (mock *TxMock) DeleteDocumentCommentsByBranchIDCalls() []struct {
return calls
}
// DetachDocumentHooksByBranchID calls DetachDocumentHooksByBranchIDFunc.
func (mock *TxMock) DetachDocumentHooksByBranchID(ctx context.Context, branchID xid.ID, organizationID string) error {
callInfo := struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
}{
Ctx: ctx,
BranchID: branchID,
OrganizationID: organizationID,
}
mock.lockDetachDocumentHooksByBranchID.Lock()
mock.calls.DetachDocumentHooksByBranchID = append(mock.calls.DetachDocumentHooksByBranchID, callInfo)
mock.lockDetachDocumentHooksByBranchID.Unlock()
if mock.DetachDocumentHooksByBranchIDFunc == nil {
var (
errOut error
)
return errOut
}
return mock.DetachDocumentHooksByBranchIDFunc(ctx, branchID, organizationID)
}
// DetachDocumentHooksByBranchIDCalls gets all the calls that were made to DetachDocumentHooksByBranchID.
// Check the length with:
//
// len(mockedTx.DetachDocumentHooksByBranchIDCalls())
func (mock *TxMock) DetachDocumentHooksByBranchIDCalls() []struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
} {
var calls []struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
}
mock.lockDetachDocumentHooksByBranchID.RLock()
calls = mock.calls.DetachDocumentHooksByBranchID
mock.lockDetachDocumentHooksByBranchID.RUnlock()
return calls
}
// FetchBranchReviewer calls FetchBranchReviewerFunc.
func (mock *TxMock) FetchBranchReviewer(ctx context.Context, branchID xid.ID, userID string, organizationID string) (*documentCore.BranchReviewer, error) {
callInfo := struct {
@ -1940,7 +1985,9 @@ func (mock *TxMock) ReplaceBranchTags(ctx context.Context, organizationID string
mock.calls.ReplaceBranchTags = append(mock.calls.ReplaceBranchTags, callInfo)
mock.lockReplaceBranchTags.Unlock()
if mock.ReplaceBranchTagsFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.ReplaceBranchTagsFunc(ctx, organizationID, fromBranchID, toBranchID)
@ -1998,49 +2045,6 @@ func (mock *TxMock) RollbackCalls() []struct {
return calls
}
// DetachDocumentHooksByBranchID calls DetachDocumentHooksByBranchIDFunc.
func (mock *TxMock) DetachDocumentHooksByBranchID(ctx context.Context, branchID xid.ID, organizationID string) error {
callInfo := struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
}{
Ctx: ctx,
BranchID: branchID,
OrganizationID: organizationID,
}
mock.lockDetachDocumentHooksByBranchID.Lock()
mock.calls.DetachDocumentHooksByBranchID = append(mock.calls.DetachDocumentHooksByBranchID, callInfo)
mock.lockDetachDocumentHooksByBranchID.Unlock()
if mock.DetachDocumentHooksByBranchIDFunc == nil {
var (
errOut error
)
return errOut
}
return mock.DetachDocumentHooksByBranchIDFunc(ctx, branchID, organizationID)
}
// DetachDocumentHooksByBranchIDCalls gets all the calls that were made to DetachDocumentHooksByBranchID.
// Check the length with:
//
// len(mockedTx.DetachDocumentHooksByBranchIDCalls())
func (mock *TxMock) DetachDocumentHooksByBranchIDCalls() []struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
} {
var calls []struct {
Ctx context.Context
BranchID xid.ID
OrganizationID string
}
mock.lockDetachDocumentHooksByBranchID.RLock()
calls = mock.calls.DetachDocumentHooksByBranchID
mock.lockDetachDocumentHooksByBranchID.RUnlock()
return calls
}
// UpdateBranchReviewer calls UpdateBranchReviewerFunc.
func (mock *TxMock) UpdateBranchReviewer(ctx context.Context, reviewer documentCore.BranchReviewer) error {
callInfo := struct {

View File

@ -236,7 +236,9 @@ func (mock *DBMock) AssignBranchTag(ctx context.Context, organizationID string,
mock.calls.AssignBranchTag = append(mock.calls.AssignBranchTag, callInfo)
mock.lockAssignBranchTag.Unlock()
if mock.AssignBranchTagFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.AssignBranchTagFunc(ctx, organizationID, documentID, branchID, tagID)
@ -593,7 +595,9 @@ func (mock *DBMock) InsertTag(ctx context.Context, t tag.Tag) error {
mock.calls.InsertTag = append(mock.calls.InsertTag, callInfo)
mock.lockInsertTag.Unlock()
if mock.InsertTagFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.InsertTagFunc(ctx, t)

View File

@ -17,7 +17,7 @@ var _ SearchTrigger = &SearchTriggerMock{}
//
// // make and configure a mocked SearchTrigger
// mockedSearchTrigger := &SearchTriggerMock{
// TriggerFunc: func() {
// TriggerFunc: func() {
// panic("mock out the Trigger method")
// },
// }

View File

@ -242,7 +242,9 @@ func (mock *TxMock) AssignBranchTag(ctx context.Context, organizationID string,
mock.calls.AssignBranchTag = append(mock.calls.AssignBranchTag, callInfo)
mock.lockAssignBranchTag.Unlock()
if mock.AssignBranchTagFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.AssignBranchTagFunc(ctx, organizationID, documentID, branchID, tagID)
@ -590,7 +592,9 @@ func (mock *TxMock) InsertTag(ctx context.Context, t tag.Tag) error {
mock.calls.InsertTag = append(mock.calls.InsertTag, callInfo)
mock.lockInsertTag.Unlock()
if mock.InsertTagFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.InsertTagFunc(ctx, t)

View File

@ -192,7 +192,9 @@ func (mock *DBMock) AssignBranchTag(ctx context.Context, organizationID string,
mock.calls.AssignBranchTag = append(mock.calls.AssignBranchTag, callInfo)
mock.lockAssignBranchTag.Unlock()
if mock.AssignBranchTagFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.AssignBranchTagFunc(ctx, organizationID, documentID, branchID, tagID)
@ -237,7 +239,9 @@ func (mock *DBMock) DeleteTag(ctx context.Context, id xid.ID, organizationID str
mock.calls.DeleteTag = append(mock.calls.DeleteTag, callInfo)
mock.lockDeleteTag.Unlock()
if mock.DeleteTagFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.DeleteTagFunc(ctx, id, organizationID)
@ -281,10 +285,10 @@ func (mock *DBMock) FetchBranchTagIDs(ctx context.Context, organizationID string
mock.lockFetchBranchTagIDs.Unlock()
if mock.FetchBranchTagIDsFunc == nil {
var (
idsOut []xid.ID
iDsOut []xid.ID
errOut error
)
return idsOut, errOut
return iDsOut, errOut
}
return mock.FetchBranchTagIDsFunc(ctx, organizationID, documentID, branchID)
}
@ -368,7 +372,9 @@ func (mock *DBMock) InsertTag(ctx context.Context, t tagCore.Tag) error {
mock.calls.InsertTag = append(mock.calls.InsertTag, callInfo)
mock.lockInsertTag.Unlock()
if mock.InsertTagFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.InsertTagFunc(ctx, t)
@ -411,7 +417,9 @@ func (mock *DBMock) SetTagVisibility(ctx context.Context, organizationID string,
mock.calls.SetTagVisibility = append(mock.calls.SetTagVisibility, callInfo)
mock.lockSetTagVisibility.Unlock()
if mock.SetTagVisibilityFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.SetTagVisibilityFunc(ctx, organizationID, userID, id, inp)
@ -460,7 +468,9 @@ func (mock *DBMock) UnassignBranchTag(ctx context.Context, organizationID string
mock.calls.UnassignBranchTag = append(mock.calls.UnassignBranchTag, callInfo)
mock.lockUnassignBranchTag.Unlock()
if mock.UnassignBranchTagFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.UnassignBranchTagFunc(ctx, organizationID, documentID, branchID, tagID)
@ -505,7 +515,9 @@ func (mock *DBMock) UpdateTagTree(ctx context.Context, tree tagCore.Summaries, o
mock.calls.UpdateTagTree = append(mock.calls.UpdateTagTree, callInfo)
mock.lockUpdateTagTree.Unlock()
if mock.UpdateTagTreeFunc == nil {
var errOut error
var (
errOut error
)
return errOut
}
return mock.UpdateTagTreeFunc(ctx, tree, organizationID)

View File

@ -313,8 +313,65 @@ describe("<MetricMainBlock>", { concurrent: false }, () => {
)
await nextTick()
expect(updateAttributes).toHaveBeenCalledTimes(1)
expect(updateAttributes).toHaveBeenCalledWith({
simulationPreset: MetricSimulationPreset.HTTPLatency,
simulationActive: true,
})
})
// only the server switches the flag off. An edit that neither starts
// nor stops the simulation does not send it
it("changes only the preset of a block that is simulating", async ({
expect,
}) => {
const updateAttributes = vi.fn()
const uid = nextUid()
await mountBlock({
uid: uid,
updateAttributes: updateAttributes,
attrs: {
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
},
underTooltipProvider: true,
})
const config = storedConfig(uid)
if (config) {
config.simulationPreset = MetricSimulationPreset.DiskUsage
}
expect(updateAttributes).toHaveBeenCalledTimes(1)
expect(updateAttributes).toHaveBeenCalledWith({
simulationPreset: MetricSimulationPreset.DiskUsage,
})
})
it("stops a simulation by unsetting the preset and the flag together", async ({
expect,
}) => {
const updateAttributes = vi.fn()
const uid = nextUid()
await mountBlock({
uid: uid,
updateAttributes: updateAttributes,
attrs: {
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
},
underTooltipProvider: true,
})
const config = storedConfig(uid)
if (config) {
config.simulationPreset = null
}
expect(updateAttributes).toHaveBeenCalledTimes(1)
expect(updateAttributes).toHaveBeenCalledWith({
simulationPreset: null,
simulationActive: false,
})
})
@ -325,13 +382,30 @@ describe("<MetricMainBlock>", { concurrent: false }, () => {
await mountBlock({
uid: uid,
attrs: { simulationPreset: MetricSimulationPreset.DiskUsage },
attrs: {
simulationPreset: MetricSimulationPreset.DiskUsage,
simulationActive: true,
},
underTooltipProvider: true,
})
expect(storedConfig(uid)?.simulationPreset).toBe(
MetricSimulationPreset.DiskUsage,
)
expect(storedConfig(uid)?.simulationActive).toBe(true)
})
it("reads a block without a simulation flag as not simulating", async ({
expect,
}) => {
const uid = nextUid()
await mountBlock({
uid: uid,
attrs: { simulationPreset: MetricSimulationPreset.DiskUsage },
})
expect(storedConfig(uid)?.simulationActive).toBe(false)
})
it("shows the simulation icon while the visualization simulates", async ({
@ -384,6 +458,8 @@ describe("<MetricMainBlock>", { concurrent: false }, () => {
dataSourceId: "ds-1",
visualizationType: GenericQueryChartType.Gauge,
unitCustom: "req/s",
simulationPreset: null,
simulationActive: false,
config: null,
}),
)

View File

@ -86,6 +86,7 @@ function migrateLegacyIfNeeded(fieldAttrs: Record<string, any>) {
axisBoundsMin: legacy.axisBounds?.min ?? null,
axisBoundsMax: legacy.axisBounds?.max ?? null,
simulationPreset: null,
simulationActive: false,
// overlay caller's edit and clear legacy blob
...fieldAttrs,
config: null,
@ -231,9 +232,27 @@ const config = reactive({
(props.node.attrs.simulationPreset as MetricConfig["simulationPreset"]) ??
null,
set: (v) => {
migrateLegacyIfNeeded({ simulationPreset: v })
if (v === null) {
migrateLegacyIfNeeded({
simulationPreset: null,
simulationActive: false,
})
return
}
if (props.node.attrs.simulationActive === true) {
migrateLegacyIfNeeded({ simulationPreset: v })
return
}
migrateLegacyIfNeeded({ simulationPreset: v, simulationActive: true })
},
}),
simulationActive: computed<MetricConfig["simulationActive"]>(
() => props.node.attrs.simulationActive === true,
),
})
const otherEditingUsers = editingUsersRef(() => props.node.attrs.uid as string)

View File

@ -433,13 +433,72 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
])
})
it("asks for the preset the block kept from its last simulation", async ({
expect,
}) => {
mockEndpoint("GET", QUERY_URL, () => ({
status: GenericQueryResultStatus.NoData,
data: [],
}))
const wrapper = await mountContainer(
metricConfig({ simulationPreset: MetricSimulationPreset.ErrorRate }),
)
await vi.waitFor(() => {
expect(wrapper.text()).toContain(
t("editor.metrics.status.no-data-loaded.simulate-action-button"),
)
}, WAIT_FOR_OPTIONS)
await findButtonByText(
wrapper,
t("editor.metrics.status.no-data-loaded.simulate-action-button"),
).trigger("click")
expect(wrapper.emitted("simulate")).toEqual([
[MetricSimulationPreset.ErrorRate],
])
})
// the preset stays after the simulation ends, so a preset alone does
// not mean the block simulates
it("queries like any block once its simulation is switched off", async ({
expect,
}) => {
const uid = nextUid()
const checks = mockEndpoint("POST", simulationCheckURL(uid), () => ({
cleared: false,
}))
const calls = mockEndpoint("GET", QUERY_URL, () => seriesResult())
const wrapper = await mountContainer(
metricConfig({
simulationPreset: MetricSimulationPreset.HTTPLatency,
simulationActive: false,
}),
{ uid: uid },
)
await vi.waitFor(() => {
expect(
wrapper.findComponent({ name: "LineChart" }).props("seriesData"),
).toHaveLength(1)
}, WAIT_FOR_OPTIONS)
expect(calls.length).toBeGreaterThan(0)
expect(simulationVisible(wrapper)).toBe(false)
expect(checks).toHaveLength(0)
})
it("draws generated data without querying the data source", async ({
expect,
}) => {
const calls = mockEndpoint("GET", QUERY_URL, () => seriesResult())
const wrapper = await mountContainer(
metricConfig({ simulationPreset: MetricSimulationPreset.HTTPLatency }),
metricConfig({
simulationPreset: MetricSimulationPreset.HTTPLatency,
simulationActive: true,
}),
)
expect(chartNames(wrapper)).toEqual(["LineChart"])
@ -485,6 +544,7 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
const wrapper = await mountContainer(
metricConfig({
simulationPreset: "pie_of_the_day" as MetricSimulationPreset,
simulationActive: true,
}),
)
@ -510,6 +570,7 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
const wrapper = await mountContainer(
metricConfig({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
)
@ -526,7 +587,10 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
mockEndpoint("GET", QUERY_URL, () => seriesResult())
const wrapper = await mountContainer(
metricConfig({ simulationPreset: MetricSimulationPreset.CPUUsage }),
metricConfig({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
)
expect(simulationVisible(wrapper)).toBe(true)
@ -539,6 +603,7 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
metricConfig({
timeRange: TimeRangePreset.Last15Minutes,
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
)
@ -570,6 +635,7 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
metricConfig({
visualizationType: type,
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
)
@ -582,7 +648,10 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
mockEndpoint("GET", QUERY_URL, () => seriesResult())
const wrapper = await mountContainer(
metricConfig({ simulationPreset: MetricSimulationPreset.CPUUsage }),
metricConfig({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
)
expect(simulationVisible(wrapper)).toBe(true)
@ -604,7 +673,10 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
mockEndpoint("GET", QUERY_URL, () => seriesResult())
await mountContainer(
metricConfig({ simulationPreset: MetricSimulationPreset.CPUUsage }),
metricConfig({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
{ uid: uid },
)
@ -627,6 +699,7 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
metricConfig({
refreshInterval: RefreshInterval.D1,
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
{ uid: uid },
)
@ -649,7 +722,10 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
useEditorStore().updateActiveBranchId(null)
await mountContainer(
metricConfig({ simulationPreset: MetricSimulationPreset.CPUUsage }),
metricConfig({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
{ uid: uid },
)
@ -669,7 +745,10 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
}))
await mountContainer(
metricConfig({ simulationPreset: MetricSimulationPreset.CPUUsage }),
metricConfig({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
{ uid: uid, disableRefresh: true },
)
@ -692,12 +771,13 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
})
const config = metricConfig({
simulationPreset: MetricSimulationPreset.HTTPLatency,
simulationActive: true,
})
const wrapper = await mountContainer(config)
await wrapper.setProps({
config: { ...config, simulationPreset: null },
config: { ...config, simulationActive: false },
})
await nextTick()
@ -732,12 +812,13 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
{ name: "Query 2", query: "", legendFormat: "" },
],
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
})
const wrapper = await mountContainer(config)
await wrapper.setProps({
config: { ...config, simulationPreset: null },
config: { ...config, simulationActive: false },
})
await nextTick()
@ -777,12 +858,13 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
config: {
...config,
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
},
})
await nextTick()
await wrapper.setProps({
config: { ...config, simulationPreset: null },
config: { ...config, simulationActive: false },
})
await vi.waitFor(() => {
@ -799,6 +881,7 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
const calls = mockEndpoint("GET", QUERY_URL, () => seriesResult())
const config = metricConfig({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
})
const wrapper = await mountContainer(config, { uid: uid })
@ -808,7 +891,7 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
// core removes the attribute on the live document, which reaches
// the editor as a node attribute change
await wrapper.setProps({
config: { ...config, simulationPreset: null },
config: { ...config, simulationActive: false },
})
await vi.waitFor(() => {
@ -826,7 +909,10 @@ describe("<VisualizationContainer>", { concurrent: false }, () => {
})
const wrapper = await mountContainer(
metricConfig({ simulationPreset: MetricSimulationPreset.CPUUsage }),
metricConfig({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
{ uid: uid },
)

View File

@ -58,12 +58,17 @@ const dataSourceUnavailable = computed(() => {
return !!dataSource && dataSource.status !== DataSourceStatus.Success
})
// an unknown preset reads as "not simulating", so a block carrying one
// falls back to querying instead of breaking the render for every viewer
// the preset the block is drawing, null when it queries instead. An
// unknown preset reads as "not simulating", so a block carrying one falls
// back to querying instead of breaking the render for every viewer
const simulationPreset = computed(() => {
const preset: unknown = props.config.simulationPreset
if (dataSourceUnavailable.value || !isSimulationPreset(preset)) {
if (
!props.config.simulationActive ||
dataSourceUnavailable.value ||
!isSimulationPreset(preset)
) {
return null
}
@ -418,7 +423,9 @@ async function checkSimulation() {
}
function startSimulation() {
emit("simulate", DEFAULT_SIMULATION_PRESET)
const kept: unknown = props.config.simulationPreset
emit("simulate", isSimulationPreset(kept) ? kept : DEFAULT_SIMULATION_PRESET)
}
function openModal() {

View File

@ -521,12 +521,27 @@ describe("<ConfigSidebar>", { concurrent: false }, () => {
expect(wrapper.findComponent(SimulationPresetSelect).exists()).toBe(false)
})
it("stays hidden once the real data has arrived", async ({ expect }) => {
const wrapper = await mountSidebar({
modelValue: configWith({
simulationPreset: MetricSimulationPreset.HTTPLatency,
simulationActive: false,
}),
})
expect(wrapper.text()).not.toContain(
t("editor.metrics.simulation.section-title"),
)
expect(wrapper.findComponent(SimulationPresetSelect).exists()).toBe(false)
})
it("explains the simulation and shows the chart it draws", async ({
expect,
}) => {
const wrapper = await mountSidebar({
modelValue: configWith({
simulationPreset: MetricSimulationPreset.HTTPLatency,
simulationActive: true,
}),
})
@ -544,6 +559,7 @@ describe("<ConfigSidebar>", { concurrent: false }, () => {
it("switches the block to another preset", async ({ expect }) => {
const config = configWith({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
})
const wrapper = await mountSidebar({ modelValue: config })
@ -561,6 +577,7 @@ describe("<ConfigSidebar>", { concurrent: false }, () => {
it("turns the simulation off again", async ({ expect }) => {
const config = configWith({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
})
const wrapper = await mountSidebar({ modelValue: config })
@ -572,12 +589,133 @@ describe("<ConfigSidebar>", { concurrent: false }, () => {
expect(config.simulationPreset).toBeNull()
})
// a diff compares what each side chose. It shows the preset even
// when the simulation is already off
it("shows the preset a diff replaced beside the new one", async ({
expect,
}) => {
const wrapper = await mountSidebar({
modelValue: configWith({
simulationPreset: MetricSimulationPreset.DiskUsage,
simulationActive: false,
}),
diffStatus: DiffStatus.Modified,
oldConfig: configWith({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: false,
}),
})
const selects = wrapper.findAllComponents(SimulationPresetSelect)
expect(selects).toHaveLength(2)
expect(at(selects, 0).text()).toContain(
t("editor.metrics.simulation.preset-options.disk_usage"),
)
expect(at(selects, 0).find("button").classes()).toContain(
"bg-diff-field-added",
)
expect(at(selects, 1).text()).toContain(
t("editor.metrics.simulation.preset-options.cpu_usage"),
)
expect(at(selects, 1).find("button").classes()).toContain(
"bg-diff-field-removed",
)
})
it("shows only the new side of a preset a diff added", async ({
expect,
}) => {
const wrapper = await mountSidebar({
modelValue: configWith({
simulationPreset: MetricSimulationPreset.DiskUsage,
simulationActive: true,
}),
diffStatus: DiffStatus.Modified,
oldConfig: configWith(),
})
const selects = wrapper.findAllComponents(SimulationPresetSelect)
expect(selects).toHaveLength(1)
expect(at(selects, 0).find("button").classes()).toContain(
"bg-diff-field-added",
)
})
it("shows only the old side of a preset a diff removed", async ({
expect,
}) => {
const wrapper = await mountSidebar({
modelValue: configWith(),
diffStatus: DiffStatus.Modified,
oldConfig: configWith({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
})
const selects = wrapper.findAllComponents(SimulationPresetSelect)
expect(wrapper.text()).toContain(
t("editor.metrics.simulation.section-title"),
)
expect(selects).toHaveLength(1)
expect(at(selects, 0).text()).toContain(
t("editor.metrics.simulation.preset-options.cpu_usage"),
)
expect(at(selects, 0).find("button").classes()).toContain(
"bg-diff-field-removed",
)
})
it("leaves a preset both sides share unmarked", async ({ expect }) => {
const wrapper = await mountSidebar({
modelValue: configWith({
title: "After",
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: false,
}),
diffStatus: DiffStatus.Modified,
oldConfig: configWith({
title: "Before",
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
})
const selects = wrapper.findAllComponents(SimulationPresetSelect)
expect(selects).toHaveLength(1)
expect(at(selects, 0).find("button").classes()).not.toContain(
"bg-diff-field-added",
)
})
it("does not offer the switch-off button in a diff", async ({ expect }) => {
const wrapper = await mountSidebar({
modelValue: configWith({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
diffStatus: DiffStatus.Unchanged,
})
expect(wrapper.text()).toContain(
t("editor.metrics.simulation.section-title"),
)
expect(wrapper.text()).not.toContain(
t("editor.metrics.simulation.stop-button"),
)
})
it("does not offer a reader the switch-off button", async ({ expect }) => {
useEditorMeta().setEditable(false)
const wrapper = await mountSidebar({
modelValue: configWith({
simulationPreset: MetricSimulationPreset.CPUUsage,
simulationActive: true,
}),
})

View File

@ -155,6 +155,24 @@ const isBoundsMaxModified = computed(() => {
props.oldConfig.axisBounds.max !== config.value.axisBounds.max
)
})
const isSimulationPresetModified = computed(() => {
return (
isDiffModified.value &&
!!props.oldConfig &&
props.oldConfig.simulationPreset !== config.value.simulationPreset
)
})
// outside a diff the section shows only while the block simulates.
// A diff shows it when either side has a preset.
const isSimulationSectionVisible = computed(() => {
if (props.diffStatus) {
return (
!!config.value.simulationPreset || !!props.oldConfig?.simulationPreset
)
}
return !!config.value.simulationPreset && config.value.simulationActive
})
interface ThresholdEntry {
value?: number
@ -287,7 +305,7 @@ function removeThreshold(index: number) {
</div>
<div class="mt-1 h-px bg-border" />
</div>
<div v-if="config.simulationPreset">
<div v-if="isSimulationSectionVisible">
<div class="flex flex-col gap-1 px-1.75">
<span class="text-2sm font-medium">
{{ $t("editor.metrics.simulation.section-title") }}
@ -301,10 +319,21 @@ function removeThreshold(index: number) {
{{ $t("editor.metrics.simulation.preset-label") }}
</span>
</template>
<SimulationPresetSelect v-model="config.simulationPreset" />
<SimulationPresetSelect
v-if="config.simulationPreset"
v-model="config.simulationPreset"
:diff-status="isSimulationPresetModified ? DiffStatus.Added : null"
/>
<SimulationPresetSelect
v-if="
isSimulationPresetModified && props.oldConfig?.simulationPreset
"
:model-value="props.oldConfig.simulationPreset"
:diff-status="DiffStatus.Removed"
/>
</ConfigField>
<ShadcnUiButton
v-if="!isEditingDisabled"
v-if="!isEditingDisabled && !props.diffStatus"
variant="outline-transparent"
size="custom"
class="h-[1.775rem]! justify-center px-1.5 text-2sm font-normal"

View File

@ -1,8 +1,12 @@
<script lang="ts" setup>
import { cn } from "~/lib/utils"
import { MetricSimulationPreset } from "../utils"
import { DiffStatus } from "~/components/editor/diff/position-map"
const selectedPreset = defineModel<MetricSimulationPreset | null>()
const props = defineProps<{
diffStatus?: DiffStatus | null
}>()
const { isEditable } = useEditorMeta()
const editorStore = useEditorStore()
@ -26,6 +30,8 @@ function selectPreset(preset: MetricSimulationPreset) {
cn(
'h-[1.775rem]! justify-between gap-1 px-1.5 text-2sm font-normal opacity-100!',
isEditingDisabled && 'pointer-events-none select-text',
props.diffStatus === DiffStatus.Added && 'bg-diff-field-added',
props.diffStatus === DiffStatus.Removed && 'bg-diff-field-removed',
)
"
>

View File

@ -238,6 +238,7 @@ describe("MetricBlock", () => {
axisBoundsMin: null,
axisBoundsMax: null,
simulationPreset: null,
simulationActive: false,
width: MetricBlockWidth.Standard,
})
})
@ -274,6 +275,10 @@ describe("MetricBlock", () => {
widthAttribute().renderHTML?.({ width: MetricBlockWidth.Compact }),
).toEqual({ "data-width": MetricBlockWidth.Compact })
})
it("keeps the simulation flag out of the rendered HTML", ({ expect }) => {
expect(metricAttributes().simulationActive?.rendered).toBe(false)
})
})
describe("insertMetricBlock", () => {
@ -332,42 +337,6 @@ describe("MetricBlock", () => {
})
})
// the width attribute owns the only parse/render pair among the block
// attributes, so the tests reach it through the declaration
function widthAttribute() {
const addAttributes = MetricBlock.config.addAttributes
if (!addAttributes) {
throw new Error("MetricBlock declares no attributes")
}
const attributes = addAttributes.call({} as never) as Record<
string,
{
parseHTML?: (element: HTMLElement) => unknown
renderHTML?: (attributes: Record<string, unknown>) => unknown
}
>
const width = attributes.width
if (!width) {
throw new Error("MetricBlock declares no width attribute")
}
return width
}
function metricBlockCommands() {
const addCommands = MetricBlock.config.addCommands
if (!addCommands) {
throw new Error("MetricBlock declares no commands")
}
return addCommands.call({} as never)
}
describe("setUpMetricBlock", () => {
it("wraps the metric block in a grid at the document root", ({ expect }) => {
const state = EditorState.create({
@ -478,3 +447,42 @@ describe("insertMetricBlock", () => {
])
})
})
// how an attribute reaches HTML is only visible on its declaration, so the
// tests read it from there
function metricAttributes() {
const addAttributes = MetricBlock.config.addAttributes
if (!addAttributes) {
throw new Error("MetricBlock declares no attributes")
}
return addAttributes.call({} as never) as Record<
string,
{
rendered?: boolean
parseHTML?: (element: HTMLElement) => unknown
renderHTML?: (attributes: Record<string, unknown>) => unknown
}
>
}
function widthAttribute() {
const width = metricAttributes().width
if (!width) {
throw new Error("MetricBlock declares no width attribute")
}
return width
}
function metricBlockCommands() {
const addCommands = MetricBlock.config.addCommands
if (!addCommands) {
throw new Error("MetricBlock declares no commands")
}
return addCommands.call({} as never)
}

View File

@ -114,6 +114,10 @@ export const MetricBlock = Node.create({
axisBoundsMin: { default: defaults.axisBounds.min ?? null },
axisBoundsMax: { default: defaults.axisBounds.max ?? null },
simulationPreset: { default: defaults.simulationPreset },
simulationActive: {
default: defaults.simulationActive,
rendered: false,
},
width: {
default: MetricBlockWidth.Standard,
parseHTML: (element) =>

View File

@ -15,6 +15,10 @@ export const DEFAULT_SIMULATION_PRESET = MetricSimulationPreset.CPUUsage
// metric went live.
export const SIMULATION_CHECK_INTERVAL_MS = 10_000
// the node attribute behind MetricConfig.simulationActive. The server
// sets it, not a writer, so a branch diff leaves it out
export const SIMULATION_ACTIVE_ATTR = "simulationActive"
// how many points a simulated series aims for across its time range. The
// step derived from it is what buckets are aligned to, so a window that
// slides forward keeps the points it already showed.

View File

@ -39,6 +39,7 @@ describe("defaultMetricConfig", () => {
unit: { type: null, custom: null },
axisBounds: { min: null, max: null },
simulationPreset: null,
simulationActive: false,
})
})
@ -86,6 +87,7 @@ describe("buildConfigFromNodeAttrs", () => {
unit: { type: VisualizationTimeUnit.Seconds, custom: null },
axisBounds: { min: 0, max: 100 },
simulationPreset: null,
simulationActive: false,
})
})
@ -113,6 +115,7 @@ describe("buildConfigFromNodeAttrs", () => {
unit: { type: null, custom: null },
axisBounds: { min: null, max: null },
simulationPreset: null,
simulationActive: false,
})
})
@ -146,6 +149,7 @@ describe("buildConfigFromNodeAttrs", () => {
unit: { type: VisualizationDataUnit.Megabytes, custom: "reqs" },
axisBounds: { min: -1, max: 1 },
simulationPreset: null,
simulationActive: false,
})
})
@ -157,6 +161,27 @@ describe("buildConfigFromNodeAttrs", () => {
).toBe(MetricSimulationPreset.HTTPLatency)
})
it.for([
{ name: "a set flag", input: true, expected: true },
{ name: "a cleared flag", input: false, expected: false },
{
name: "a block saved before the flag existed",
input: undefined,
expected: false,
},
{ name: "a flag that is not a boolean", input: "true", expected: false },
])(
"reads $name as simulating: $expected",
({ input, expected }, { expect }) => {
expect(
buildConfigFromNodeAttrs({
simulationPreset: MetricSimulationPreset.HTTPLatency,
simulationActive: input,
}).simulationActive,
).toBe(expected)
},
)
it("fills defaults when there are no attributes at all", ({ expect }) => {
expect(buildConfigFromNodeAttrs({})).toEqual({
title: "",
@ -171,6 +196,7 @@ describe("buildConfigFromNodeAttrs", () => {
unit: { type: null, custom: null },
axisBounds: { min: null, max: null },
simulationPreset: null,
simulationActive: false,
})
})
})

View File

@ -106,9 +106,13 @@ export interface MetricConfig {
min?: number | null
max?: number | null
}
// non-null while the block renders generated data instead of querying
// its data source
// the generated series the block draws while simulationActive is set.
// It stays after the simulation ends, so a block that simulates again
// reuses it. Null on a block nobody has simulated
simulationPreset: MetricSimulationPreset | null
// true while the block renders its preset instead of querying its data
// source. The server switches it off once real data answers
simulationActive: boolean
}
export function defaultMetricConfig(): MetricConfig {
@ -135,6 +139,7 @@ export function defaultMetricConfig(): MetricConfig {
max: null,
},
simulationPreset: null,
simulationActive: false,
}
}
@ -171,6 +176,7 @@ export function buildConfigFromNodeAttrs(
},
// the legacy blob predates simulation, so it never carries one
simulationPreset: null,
simulationActive: false,
}
}
@ -198,6 +204,7 @@ export function buildConfigFromNodeAttrs(
simulationPreset:
(attrs.simulationPreset as MetricSimulationPreset | null | undefined) ??
null,
simulationActive: attrs.simulationActive === true,
}
}

View File

@ -105,6 +105,36 @@ describe("computeMergedDocument", () => {
])
})
it.for([
{
name: "a metric block whose only change is its simulation preset as modified",
original: { simulationPreset: "cpu_usage", simulationActive: false },
modified: { simulationPreset: "error_rate", simulationActive: false },
expected: DiffStatus.Modified,
},
{
name: "a metric block whose only change is its simulation flag as unchanged",
original: { simulationPreset: "cpu_usage", simulationActive: true },
modified: { simulationPreset: "cpu_usage", simulationActive: false },
expected: DiffStatus.Unchanged,
},
])("marks $name", ({ original, modified, expected }) => {
const grid = (attrs: Record<string, unknown>): JSONContent => ({
type: "metricGrid",
attrs: { uid: "g1" },
content: [{ type: "metricBlock", attrs: { uid: "m1", ...attrs } }],
})
const result = computeMergedDocument(
doc(grid(original)),
doc(grid(modified)),
)
expect(result.doc.content?.[0]?.content?.[0]?.attrs?.diffStatus).toBe(
expected,
)
})
it("matches blocks without uids by content through the LCS fallback", () => {
const original = doc(paragraph("same"), paragraph("gone"))
const modified = doc(paragraph("same"), paragraph("new"))

View File

@ -17,6 +17,7 @@ import Paragraph from "@tiptap/extension-paragraph"
import HorizontalRule from "@tiptap/extension-horizontal-rule"
import { COMMENT_MARK_NAME } from "~/components/editor/mark-names"
import { NODE_COMMENT_ID_ATTR } from "~/components/editor/comments/node-comment-extension"
import { SIMULATION_ACTIVE_ATTR } from "~/components/editor/blocks/metrics/simulation"
import {
BulletList,
OrderedList,
@ -56,7 +57,7 @@ export const MODIFIED_TEXT_CONTENT_TYPES = new Set<string>([MERMAID_BLOCK_NAME])
export const DEFAULT_MERGE_OPTIONS: MergeOptions = {
excludeMarks: [COMMENT_MARK_NAME],
excludeAttributes: [NODE_COMMENT_ID_ATTR],
excludeAttributes: [NODE_COMMENT_ID_ATTR, SIMULATION_ACTIVE_ATTR],
useUidMatching: true,
uidAttribute: "uid",
unwrapTypes: [

View File

@ -35,6 +35,42 @@ describe("hashBlockContent", () => {
expect(hashBlockContent(commented)).toBe(hashBlockContent(clean))
})
// the server switches a simulation off on whichever branch is open.
// That is not a user change, so the hash ignores the flag
it("ignores a metric block's simulation flag by default", () => {
const simulating = {
type: "metricBlock",
attrs: {
uid: "m1",
simulationPreset: "cpu_usage",
simulationActive: true,
},
}
const answered = {
type: "metricBlock",
attrs: {
uid: "m1",
simulationPreset: "cpu_usage",
simulationActive: false,
},
}
expect(hashBlockContent(simulating)).toBe(hashBlockContent(answered))
})
it("keeps a metric block's simulation preset significant", () => {
const a = {
type: "metricBlock",
attrs: { uid: "m1", simulationPreset: "cpu_usage" },
}
const b = {
type: "metricBlock",
attrs: { uid: "m1", simulationPreset: "error_rate" },
}
expect(hashBlockContent(a)).not.toBe(hashBlockContent(b))
})
it("merges text runs split by a comment boundary", () => {
const split = paragraph([
text("hel", [{ type: "comment", attrs: { commentId: "c1" } }]),

View File

@ -1,6 +1,7 @@
import type { JSONContent } from "@tiptap/core"
import { COMMENT_MARK_NAME } from "~/components/editor/mark-names"
import { NODE_COMMENT_ID_ATTR } from "~/components/editor/comments/node-comment-extension"
import { SIMULATION_ACTIVE_ATTR } from "~/components/editor/blocks/metrics/simulation"
export interface HashOptions {
/** mark types to strip before hashing (e.g. ['comment']) */
@ -11,7 +12,7 @@ export interface HashOptions {
const DEFAULT_OPTIONS: HashOptions = {
excludeMarks: [COMMENT_MARK_NAME],
excludeAttributes: [NODE_COMMENT_ID_ATTR],
excludeAttributes: [NODE_COMMENT_ID_ATTR, SIMULATION_ACTIVE_ATTR],
}
/**

View File

@ -28,6 +28,7 @@ function metricConfig(title: string): MetricConfig {
unit: {},
axisBounds: {},
simulationPreset: null,
simulationActive: false,
}
}