| 
									
										
										
										
											2025-01-31 21:43:58 +08:00
										 |  |  | import { openai } from '@grafana/llm'; | 
					
						
							| 
									
										
										
										
											2023-10-25 23:38:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-27 23:30:49 +08:00
										 |  |  | import { DASHBOARD_SCHEMA_VERSION } from '../../state/DashboardMigrator'; | 
					
						
							| 
									
										
										
										
											2023-10-11 21:56:42 +08:00
										 |  |  | import { createDashboardModelFixture, createPanelSaveModel } from '../../state/__fixtures__/dashboardFixtures'; | 
					
						
							| 
									
										
										
										
											2024-07-13 01:49:12 +08:00
										 |  |  | import { NEW_PANEL_TITLE } from '../../utils/dashboard'; | 
					
						
							| 
									
										
										
										
											2023-09-21 20:41:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-13 01:49:12 +08:00
										 |  |  | import { getDashboardChanges, getPanelStrings, isLLMPluginEnabled, sanitizeReply } from './utils'; | 
					
						
							| 
									
										
										
										
											2023-09-19 21:40:33 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-31 21:43:58 +08:00
										 |  |  | // Mock the openai module
 | 
					
						
							|  |  |  | jest.mock('@grafana/llm', () => ({ | 
					
						
							|  |  |  |   ...jest.requireActual('@grafana/llm'), | 
					
						
							|  |  |  |   openai: { | 
					
						
							|  |  |  |     streamChatCompletions: jest.fn(), | 
					
						
							|  |  |  |     accumulateContent: jest.fn(), | 
					
						
							|  |  |  |     health: jest.fn(), | 
					
						
							| 
									
										
										
										
											2023-09-19 21:40:33 +08:00
										 |  |  |   }, | 
					
						
							|  |  |  | })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-27 21:55:48 +08:00
										 |  |  | jest.mock('@grafana/runtime', () => ({ | 
					
						
							|  |  |  |   ...jest.requireActual('@grafana/runtime'), | 
					
						
							|  |  |  |   config: { | 
					
						
							|  |  |  |     ...jest.requireActual('@grafana/runtime').config, | 
					
						
							|  |  |  |     apps: { | 
					
						
							|  |  |  |       'grafana-llm-app': true, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }, | 
					
						
							|  |  |  | })); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-21 20:41:49 +08:00
										 |  |  | describe('getDashboardChanges', () => { | 
					
						
							|  |  |  |   it('should correctly split user changes and migration changes', () => { | 
					
						
							|  |  |  |     // Mock data for testing
 | 
					
						
							|  |  |  |     const deprecatedOptions = { | 
					
						
							|  |  |  |       legend: { displayMode: 'hidden', showLegend: false }, | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2023-10-27 23:30:49 +08:00
										 |  |  |     const deprecatedVersion = DASHBOARD_SCHEMA_VERSION - 1; | 
					
						
							| 
									
										
										
										
											2023-09-21 20:41:49 +08:00
										 |  |  |     const dashboard = createDashboardModelFixture({ | 
					
						
							|  |  |  |       schemaVersion: deprecatedVersion, | 
					
						
							| 
									
										
										
										
											2023-10-11 21:56:42 +08:00
										 |  |  |       panels: [createPanelSaveModel({ title: 'Panel 1', options: deprecatedOptions })], | 
					
						
							| 
									
										
										
										
											2023-09-21 20:41:49 +08:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Update title for the first panel
 | 
					
						
							|  |  |  |     dashboard.updatePanels([ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         ...dashboard.panels[0], | 
					
						
							|  |  |  |         title: 'New title', | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       ...dashboard.panels.slice(1), | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Call the function to test
 | 
					
						
							|  |  |  |     const result = getDashboardChanges(dashboard); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Assertions
 | 
					
						
							| 
									
										
										
										
											2023-10-28 13:10:47 +08:00
										 |  |  |     expect(result.migrationChanges).toContain( | 
					
						
							|  |  |  |       `-  "schemaVersion": ${deprecatedVersion},\n+  "schemaVersion": ${DASHBOARD_SCHEMA_VERSION},\n` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(result.migrationChanges).not.toContain( | 
					
						
							|  |  |  |       '   "panels": [\n' + | 
					
						
							| 
									
										
										
										
											2023-10-14 08:02:45 +08:00
										 |  |  |         '     {\n' + | 
					
						
							| 
									
										
										
										
											2023-10-28 13:10:47 +08:00
										 |  |  |         '-      "type": "timeseries",\n' + | 
					
						
							|  |  |  |         '-      "title": "Panel 1",\n' + | 
					
						
							|  |  |  |         '+      "id": 1,\n' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(result.migrationChanges).not.toContain( | 
					
						
							|  |  |  |       '-      }\n' + | 
					
						
							|  |  |  |         '+      },\n' + | 
					
						
							|  |  |  |         '+      "title": "New title",\n' + | 
					
						
							|  |  |  |         '+      "type": "timeseries"\n' + | 
					
						
							|  |  |  |         '     }\n' + | 
					
						
							|  |  |  |         '   ]\n' + | 
					
						
							|  |  |  |         ' }\n' | 
					
						
							| 
									
										
										
										
											2023-10-14 08:02:45 +08:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2023-10-28 13:10:47 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     expect(result.userChanges).not.toContain('-  "schemaVersion": 37,\n' + '+  "schemaVersion": 38,\n'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(result.userChanges).toContain( | 
					
						
							|  |  |  |       '   "panels": [\n' + | 
					
						
							| 
									
										
										
										
											2023-10-14 08:02:45 +08:00
										 |  |  |         '     {\n' + | 
					
						
							|  |  |  |         '-      "type": "timeseries",\n' + | 
					
						
							|  |  |  |         '-      "title": "Panel 1",\n' + | 
					
						
							| 
									
										
										
										
											2023-10-28 13:10:47 +08:00
										 |  |  |         '+      "id": 1,\n' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(result.userChanges).toContain( | 
					
						
							|  |  |  |       '-      }\n' + | 
					
						
							| 
									
										
										
										
											2023-10-14 08:02:45 +08:00
										 |  |  |         '+      },\n' + | 
					
						
							|  |  |  |         '+      "title": "New title",\n' + | 
					
						
							|  |  |  |         '+      "type": "timeseries"\n' + | 
					
						
							|  |  |  |         '     }\n' + | 
					
						
							|  |  |  |         '   ]\n' + | 
					
						
							| 
									
										
										
										
											2023-10-28 13:10:47 +08:00
										 |  |  |         ' }\n' | 
					
						
							| 
									
										
										
										
											2023-10-14 08:02:45 +08:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2023-10-28 13:10:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-21 20:41:49 +08:00
										 |  |  |     expect(result.migrationChanges).toBeDefined(); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2023-09-27 22:47:06 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe('isLLMPluginEnabled', () => { | 
					
						
							| 
									
										
										
										
											2024-05-11 02:52:59 +08:00
										 |  |  |   it('should return false if LLM plugin is not enabled', async () => { | 
					
						
							| 
									
										
										
										
											2025-01-31 21:43:58 +08:00
										 |  |  |     // Mock openai.health to return false
 | 
					
						
							|  |  |  |     jest.mocked(openai.health).mockResolvedValue({ ok: false, configured: false }); | 
					
						
							| 
									
										
										
										
											2023-09-27 22:47:06 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const enabled = await isLLMPluginEnabled(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-11 02:52:59 +08:00
										 |  |  |     expect(enabled).toBe(false); | 
					
						
							| 
									
										
										
										
											2023-09-27 22:47:06 +08:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-11 02:52:59 +08:00
										 |  |  |   it('should return true if LLM plugin is enabled', async () => { | 
					
						
							| 
									
										
										
										
											2025-01-31 21:43:58 +08:00
										 |  |  |     // Mock openai.health to return true
 | 
					
						
							|  |  |  |     jest.mocked(openai.health).mockResolvedValue({ ok: true, configured: false }); | 
					
						
							| 
									
										
										
										
											2023-09-27 22:47:06 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const enabled = await isLLMPluginEnabled(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-11 02:52:59 +08:00
										 |  |  |     expect(enabled).toBe(true); | 
					
						
							| 
									
										
										
										
											2023-09-27 22:47:06 +08:00
										 |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2023-10-06 20:11:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe('sanitizeReply', () => { | 
					
						
							|  |  |  |   it('should remove quotes from the beginning and end of a string', () => { | 
					
						
							|  |  |  |     expect(sanitizeReply('"Hello, world!"')).toBe('Hello, world!'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should not remove quotes from the middle of a string', () => { | 
					
						
							|  |  |  |     expect(sanitizeReply('Hello, "world"!')).toBe('Hello, "world"!'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should only remove quotes if they are at the beginning or end of a string, and not in the middle', () => { | 
					
						
							|  |  |  |     expect(sanitizeReply('"Hello", world!')).toBe('Hello", world!'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should return an empty string if given an empty string', () => { | 
					
						
							|  |  |  |     expect(sanitizeReply('')).toBe(''); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2024-07-13 01:49:12 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe('getPanelStrings', () => { | 
					
						
							|  |  |  |   function dashboardSetup(items: Array<{ title: string; description: string }>) { | 
					
						
							|  |  |  |     return createDashboardModelFixture({ | 
					
						
							|  |  |  |       panels: items.map((item) => createPanelSaveModel(item)), | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should return an empty array if all panels dont have title or descriptions', () => { | 
					
						
							|  |  |  |     const dashboard = dashboardSetup([{ title: '', description: '' }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(getPanelStrings(dashboard)).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should return an empty array if all panels have no description and panels that have title are titled "Panel title', () => { | 
					
						
							|  |  |  |     const dashboard = dashboardSetup([{ title: NEW_PANEL_TITLE, description: '' }]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(getPanelStrings(dashboard)).toEqual([]); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should return an array of panels if a panel has a title or description', () => { | 
					
						
							|  |  |  |     const dashboard = dashboardSetup([ | 
					
						
							|  |  |  |       { title: 'Graph panel', description: '' }, | 
					
						
							|  |  |  |       { title: '', description: 'Logs' }, | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(getPanelStrings(dashboard)).toEqual([ | 
					
						
							|  |  |  |       '- Panel 0\n- Title: Graph panel', | 
					
						
							|  |  |  |       '- Panel 1\n- Title: \n- Description: Logs', | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('returns an array with title and description if both are present', () => { | 
					
						
							|  |  |  |     const dashboard = dashboardSetup([ | 
					
						
							|  |  |  |       { title: 'Graph panel', description: 'Logs' }, | 
					
						
							|  |  |  |       { title: 'Table panel', description: 'Metrics' }, | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(getPanelStrings(dashboard)).toEqual([ | 
					
						
							|  |  |  |       '- Panel 0\n- Title: Graph panel\n- Description: Logs', | 
					
						
							|  |  |  |       '- Panel 1\n- Title: Table panel\n- Description: Metrics', | 
					
						
							|  |  |  |     ]); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); |