Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions zeppelin-web-angular/e2e/models/notebook-assistant-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Locator, Page } from '@playwright/test';
import { BasePage } from './base-page';

export class NotebookAssistantPage extends BasePage {
readonly toggleButton: Locator;
readonly panel: Locator;
readonly closeButton: Locator;

constructor(page: Page) {
super(page);
this.toggleButton = page.getByRole('button', { name: 'Toggle AI Assistant' });
this.panel = page.getByRole('complementary', { name: 'AI Assistant workspace' });
this.closeButton = page.getByRole('button', { name: 'Close AI Assistant' });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { expect, test } from '@playwright/test';
import { NotebookAssistantPage } from '../../../models/notebook-assistant-page';
import { addPageAnnotationBeforeEach, createTestNotebook, PAGES, waitForZeppelinReady } from '../../../utils';

test.describe('Notebook AI Assistant shell', () => {
addPageAnnotationBeforeEach(PAGES.WORKSPACE.NOTEBOOK_ASSISTANT);

let assistant: NotebookAssistantPage;

test.beforeEach(async ({ page }) => {
assistant = new NotebookAssistantPage(page);
});

test('opens and closes from the notebook sidebar', async ({ page }) => {
await page.goto('/#/');
await waitForZeppelinReady(page);
const { noteId } = await createTestNotebook(page);
try {
await test.step('Given a notebook with the Assistant enabled', async () => {
await page.goto(`/#/notebook/${noteId}?reactAssistant=true`);
await waitForZeppelinReady(page);
await expect(assistant.toggleButton).toBeVisible();
await expect(assistant.panel).toBeHidden();
});

await test.step('When I open the Assistant', async () => {
await assistant.toggleButton.click();
});

await test.step('Then the panel appears', async () => {
await expect(assistant.toggleButton).toHaveAttribute('aria-pressed', 'true');
await expect(assistant.panel).toBeVisible();
});

await test.step('When I close the Assistant', async () => {
await assistant.closeButton.click();
});

await test.step('Then the notebook returns to its closed state', async () => {
await expect(assistant.panel).toBeHidden();
await expect(assistant.toggleButton).toHaveAttribute('aria-pressed', 'false');
});
} finally {
const response = await page.request.delete(`/api/notebook/${noteId}`);
expect(response.ok(), 'Delete the disposable notebook').toBe(true);
}
});
});
1 change: 1 addition & 0 deletions zeppelin-web-angular/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const PAGES = {
NOTEBOOK: 'src/app/pages/workspace/notebook/notebook.component',
NOTEBOOK_ACTION_BAR: 'src/app/pages/workspace/notebook/action-bar/action-bar.component',
NOTEBOOK_ADD_PARAGRAPH: 'src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component',
NOTEBOOK_ASSISTANT: 'src/app/pages/workspace/notebook/assistant/assistant-host.component',
NOTEBOOK_CORE_READ: 'src/app/pages/workspace/notebook/core-read/notebook-core-read.component',
NOTEBOOK_INTERPRETER_BINDING: 'src/app/pages/workspace/notebook/interpreter-binding/interpreter-binding.component',
NOTEBOOK_NOTE_FORM: 'src/app/pages/workspace/notebook/note-form-block/note-form-block.component',
Expand Down
2 changes: 1 addition & 1 deletion zeppelin-web-angular/projects/zeppelin-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Each React surface is behind a URL query flag, resolved by `ReactFeatureService`
| `?react=false` | disabled |
| flag absent | disabled |

Append `?react=true` to any published paragraph URL, `?reactFooter=true` to a notebook URL, `?reactConfiguration=true` to the configuration URL, or `?reactNotebookRepos=true` to the notebook repository URL to activate React mode.
Append `?react=true` to any published paragraph URL, `?reactFooter=true` or `?reactAssistant=true` to a notebook URL, `?reactConfiguration=true` to the configuration URL, or `?reactNotebookRepos=true` to the notebook repository URL to activate React mode.

## Setup

Expand Down
1 change: 1 addition & 0 deletions zeppelin-web-angular/projects/zeppelin-react/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* limitations under the License.
*/

export { AssistantWorkspace, mount as mountAssistantWorkspace } from './pages/AssistantWorkspace';
export { ConfigurationTable, mount as mountConfigurationTable } from './pages/ConfigurationTable';
export { NotebookRepoList, mount as mountNotebookRepoList } from './pages/NotebookRepoList';
export { PublishedParagraph, mount } from './pages/PublishedParagraph';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { globalStyle, style } from '@vanilla-extract/css';
import { vars } from '@/shared/ui/assistant-theme/AssistantScope.css';

export const nav = style({
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: 40,
height: 32,
padding: 0,
border: 0,
background: 'transparent',
cursor: 'pointer'
});

export const navIcon = style({
display: 'inline-grid',
placeItems: 'center',
width: 20,
height: 16,
border: `1px solid ${vars.borderStrong}`,
borderRadius: 3,
background: vars.bg,
color: vars.text,
font: '700 10px/1 Arial, sans-serif'
});

globalStyle(`${nav}[aria-pressed='true'] ${navIcon}`, { borderColor: vars.accent });

export const panel = style({
position: 'sticky',
zIndex: 25,
top: 0,
width: 'calc(var(--assistant-sidebar-width) - 40px)',
height: 'calc(100dvh - var(--assistant-panel-top, 0px))',
display: 'flex',
flexDirection: 'column',
minHeight: 0,
overflow: 'hidden',
borderRight: `1px solid ${vars.border}`,
background: vars.bg,
boxShadow: '4px 0 2px 0 rgba(0, 0, 0, 0.06)',
paddingTop: vars.space2,
'@media': {
'(max-width: 640px)': {
width: 'min(var(--assistant-sidebar-width), 100vw)',
marginRight: 'calc(-1 * min(var(--assistant-sidebar-width), 100vw))',
marginLeft: -40
}
}
});

export const header = style({
display: 'flex',
alignItems: 'center',
// The scope's border-box includes the padding and border, which the 36px content height excluded.
minHeight: 45,
padding: `${vars.space1} ${vars.space2} ${vars.space1} ${vars.space3}`,
borderBottom: `1px solid ${vars.border}`
});

export const title = style({
margin: 0,
fontSize: 'inherit',
fontWeight: 600
});

export const close = style({
position: 'absolute',
zIndex: 1,
top: 3,
right: 3,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: 40,
height: 50,
padding: 0,
border: 0,
background: 'transparent',
color: vars.textSecondary,
cursor: 'pointer',
fontSize: 20,
transition: `color ${vars.durationFast} ${vars.easeStandard}`,
selectors: {
'&:hover': { color: vars.accentTextHover }
}
});

export const resize = style({
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
width: 5,
cursor: 'col-resize',
touchAction: 'none',
'@media': {
'(max-width: 640px)': { display: 'none' }
}
});
Loading
Loading