Skip to content

Commit c433c6e

Browse files
Maja Grubicelasticmachine
andauthored
[Lens] Disable saving visualization until there are no changes to the document (#52982)
Adding unit test for new functionality Fixing type error Removing unnecessary act statements Removing unnecessary assertion Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 21d6202 commit c433c6e

File tree

2 files changed

+106
-18
lines changed

2 files changed

+106
-18
lines changed

x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ describe('Lens App', () => {
190190
(defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({
191191
id: '1234',
192192
title: 'Daaaaaaadaumching!',
193+
expression: 'valid expression',
193194
state: {
194195
query: 'fake query',
195196
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
@@ -219,6 +220,7 @@ describe('Lens App', () => {
219220
args.editorFrame = frame;
220221
(args.docStorage.load as jest.Mock).mockResolvedValue({
221222
id: '1234',
223+
expression: 'valid expression',
222224
state: {
223225
query: 'fake query',
224226
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
@@ -244,6 +246,7 @@ describe('Lens App', () => {
244246
expect.objectContaining({
245247
doc: {
246248
id: '1234',
249+
expression: 'valid expression',
247250
state: {
248251
query: 'fake query',
249252
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
@@ -294,6 +297,30 @@ describe('Lens App', () => {
294297
newTitle: string;
295298
}
296299

300+
let defaultArgs: jest.Mocked<{
301+
editorFrame: EditorFrameInstance;
302+
data: typeof dataStartMock;
303+
core: typeof core;
304+
dataShim: DataStart;
305+
storage: Storage;
306+
docId?: string;
307+
docStorage: SavedObjectStore;
308+
redirectTo: (id?: string) => void;
309+
}>;
310+
311+
beforeEach(() => {
312+
defaultArgs = makeDefaultArgs();
313+
(defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({
314+
id: '1234',
315+
title: 'My cool doc',
316+
expression: 'valid expression',
317+
state: {
318+
query: 'kuery',
319+
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
320+
},
321+
} as jest.ResolvedValue<Document>);
322+
});
323+
297324
function getButton(instance: ReactWrapper): TopNavMenuData {
298325
return (instance
299326
.find('[data-test-subj="lnsApp_topNav"]')
@@ -322,19 +349,21 @@ describe('Lens App', () => {
322349
initialDocId?: string;
323350
}) {
324351
const args = {
325-
...makeDefaultArgs(),
352+
...defaultArgs,
326353
docId: initialDocId,
327354
};
328355
args.editorFrame = frame;
329356
(args.docStorage.load as jest.Mock).mockResolvedValue({
330357
id: '1234',
358+
expression: 'kibana',
331359
state: {
332360
query: 'fake query',
333361
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
334362
},
335363
});
336364
(args.docStorage.save as jest.Mock).mockImplementation(async ({ id }) => ({
337365
id: id || 'aaa',
366+
expression: 'kibana 2',
338367
}));
339368

340369
const instance = mount(<App {...args} />);
@@ -350,13 +379,12 @@ describe('Lens App', () => {
350379
const onChange = frame.mount.mock.calls[0][1].onChange;
351380
onChange({
352381
filterableIndexPatterns: [],
353-
doc: ({ id: initialDocId } as unknown) as Document,
382+
doc: ({ id: initialDocId, expression: 'kibana 3' } as unknown) as Document,
354383
});
355384

356385
instance.update();
357386

358387
expect(getButton(instance).disableButton).toEqual(false);
359-
360388
testSave(instance, saveProps);
361389

362390
await waitForPromises();
@@ -365,7 +393,7 @@ describe('Lens App', () => {
365393
}
366394

367395
it('shows a disabled save button when the user does not have permissions', async () => {
368-
const args = makeDefaultArgs();
396+
const args = defaultArgs;
369397
args.core.application = {
370398
...args.core.application,
371399
capabilities: {
@@ -380,24 +408,51 @@ describe('Lens App', () => {
380408
expect(getButton(instance).disableButton).toEqual(true);
381409

382410
const onChange = frame.mount.mock.calls[0][1].onChange;
383-
onChange({ filterableIndexPatterns: [], doc: ('will save this' as unknown) as Document });
384-
411+
onChange({
412+
filterableIndexPatterns: [],
413+
doc: ({ id: 'will save this', expression: 'valid expression' } as unknown) as Document,
414+
});
385415
instance.update();
416+
expect(getButton(instance).disableButton).toEqual(true);
417+
});
386418

419+
it('shows a disabled save button when there are no changes to the document', async () => {
420+
const args = defaultArgs;
421+
(args.docStorage.load as jest.Mock).mockResolvedValue({
422+
id: '1234',
423+
title: 'My cool doc',
424+
expression: '',
425+
} as jest.ResolvedValue<Document>);
426+
args.editorFrame = frame;
427+
428+
const instance = mount(<App {...args} />);
387429
expect(getButton(instance).disableButton).toEqual(true);
430+
431+
const onChange = frame.mount.mock.calls[0][1].onChange;
432+
433+
act(() => {
434+
onChange({
435+
filterableIndexPatterns: [],
436+
doc: ({ id: '1234', expression: 'valid expression' } as unknown) as Document,
437+
});
438+
});
439+
instance.update();
440+
expect(getButton(instance).disableButton).toEqual(false);
388441
});
389442

390443
it('shows a save button that is enabled when the frame has provided its state', async () => {
391-
const args = makeDefaultArgs();
444+
const args = defaultArgs;
392445
args.editorFrame = frame;
393446

394447
const instance = mount(<App {...args} />);
395448

396449
expect(getButton(instance).disableButton).toEqual(true);
397450

398451
const onChange = frame.mount.mock.calls[0][1].onChange;
399-
onChange({ filterableIndexPatterns: [], doc: ('will save this' as unknown) as Document });
400-
452+
onChange({
453+
filterableIndexPatterns: [],
454+
doc: ({ id: 'will save this', expression: 'valid expression' } as unknown) as Document,
455+
});
401456
instance.update();
402457

403458
expect(getButton(instance).disableButton).toEqual(false);
@@ -413,6 +468,7 @@ describe('Lens App', () => {
413468
expect(args.docStorage.save).toHaveBeenCalledWith({
414469
id: undefined,
415470
title: 'hello there',
471+
expression: 'kibana 3',
416472
});
417473

418474
expect(args.redirectTo).toHaveBeenCalledWith('aaa');
@@ -432,6 +488,7 @@ describe('Lens App', () => {
432488
expect(args.docStorage.save).toHaveBeenCalledWith({
433489
id: undefined,
434490
title: 'hello there',
491+
expression: 'kibana 3',
435492
});
436493

437494
expect(args.redirectTo).toHaveBeenCalledWith('aaa');
@@ -451,6 +508,7 @@ describe('Lens App', () => {
451508
expect(args.docStorage.save).toHaveBeenCalledWith({
452509
id: '1234',
453510
title: 'hello there',
511+
expression: 'kibana 3',
454512
});
455513

456514
expect(args.redirectTo).not.toHaveBeenCalled();
@@ -461,14 +519,17 @@ describe('Lens App', () => {
461519
});
462520

463521
it('handles save failure by showing a warning, but still allows another save', async () => {
464-
const args = makeDefaultArgs();
522+
const args = defaultArgs;
465523
args.editorFrame = frame;
466524
(args.docStorage.save as jest.Mock).mockRejectedValue({ message: 'failed' });
467525

468526
const instance = mount(<App {...args} />);
469527

470528
const onChange = frame.mount.mock.calls[0][1].onChange;
471-
onChange({ filterableIndexPatterns: [], doc: ({ id: undefined } as unknown) as Document });
529+
onChange({
530+
filterableIndexPatterns: [],
531+
doc: ({ id: undefined, expression: 'new expression' } as unknown) as Document,
532+
});
472533

473534
instance.update();
474535

@@ -486,8 +547,32 @@ describe('Lens App', () => {
486547
});
487548

488549
describe('query bar state management', () => {
550+
let defaultArgs: jest.Mocked<{
551+
editorFrame: EditorFrameInstance;
552+
data: typeof dataStartMock;
553+
core: typeof core;
554+
dataShim: DataStart;
555+
storage: Storage;
556+
docId?: string;
557+
docStorage: SavedObjectStore;
558+
redirectTo: (id?: string) => void;
559+
}>;
560+
561+
beforeEach(() => {
562+
defaultArgs = makeDefaultArgs();
563+
(defaultArgs.docStorage.load as jest.Mock).mockResolvedValue({
564+
id: '1234',
565+
title: 'My cool doc',
566+
expression: 'valid expression',
567+
state: {
568+
query: 'kuery',
569+
datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] },
570+
},
571+
} as jest.ResolvedValue<Document>);
572+
});
573+
489574
it('uses the default time and query language settings', () => {
490-
const args = makeDefaultArgs();
575+
const args = defaultArgs;
491576
args.editorFrame = frame;
492577

493578
mount(<App {...args} />);
@@ -510,7 +595,7 @@ describe('Lens App', () => {
510595
});
511596

512597
it('updates the index patterns when the editor frame is changed', async () => {
513-
const args = makeDefaultArgs();
598+
const args = defaultArgs;
514599
args.editorFrame = frame;
515600

516601
const instance = mount(<App {...args} />);
@@ -525,7 +610,7 @@ describe('Lens App', () => {
525610
const onChange = frame.mount.mock.calls[0][1].onChange;
526611
onChange({
527612
filterableIndexPatterns: [{ id: '1', title: 'newIndex' }],
528-
doc: ({ id: undefined } as unknown) as Document,
613+
doc: ({ id: undefined, expression: 'valid expression' } as unknown) as Document,
529614
});
530615

531616
await waitForPromises();
@@ -541,7 +626,7 @@ describe('Lens App', () => {
541626
// Do it again to verify that the dirty checking is done right
542627
onChange({
543628
filterableIndexPatterns: [{ id: '2', title: 'second index' }],
544-
doc: ({ id: undefined } as unknown) as Document,
629+
doc: ({ id: undefined, expression: 'valid expression' } as unknown) as Document,
545630
});
546631

547632
await waitForPromises();
@@ -556,7 +641,7 @@ describe('Lens App', () => {
556641
});
557642

558643
it('updates the editor frame when the user changes query or time in the search bar', () => {
559-
const args = makeDefaultArgs();
644+
const args = defaultArgs;
560645
args.editorFrame = frame;
561646

562647
const instance = mount(<App {...args} />);
@@ -586,7 +671,7 @@ describe('Lens App', () => {
586671
});
587672

588673
it('updates the filters when the user changes them', () => {
589-
const args = makeDefaultArgs();
674+
const args = defaultArgs;
590675
args.editorFrame = frame;
591676

592677
const instance = mount(<App {...args} />);

x-pack/legacy/plugins/lens/public/app_plugin/app.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,10 @@ export function App({
150150
}
151151
}, [docId]);
152152

153-
const isSaveable = lastKnownDoc && core.application.capabilities.visualize.save;
153+
const isSaveable =
154+
lastKnownDoc &&
155+
lastKnownDoc.expression.length > 0 &&
156+
core.application.capabilities.visualize.save;
154157

155158
const onError = useCallback(
156159
(e: { message: string }) =>

0 commit comments

Comments
 (0)