diff --git a/app/client/ui/GridViewMenus.ts b/app/client/ui/GridViewMenus.ts index 40f05043..6deb5fea 100644 --- a/app/client/ui/GridViewMenus.ts +++ b/app/client/ui/GridViewMenus.ts @@ -1,12 +1,15 @@ import {allCommands} from 'app/client/components/commands'; import GridView from 'app/client/components/GridView'; import {makeT} from 'app/client/lib/localization'; +import {ColumnRec} from "app/client/models/entities/ColumnRec"; import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec'; import {GristTooltips} from 'app/client/ui/GristTooltips'; import {withInfoTooltip} from 'app/client/ui/tooltips'; import {testId, theme, vars} from 'app/client/ui2018/cssVars'; +import {IconName} from "app/client/ui2018/IconList"; import {icon} from 'app/client/ui2018/icons'; import { + menuCssClass, menuDivider, menuIcon, menuItem, @@ -19,11 +22,12 @@ import { searchableMenu, SearchableMenuItem, } from 'app/client/ui2018/menus'; +import * as UserType from "app/client/widgets/UserType"; +import {isListType, RecalcWhen} from "app/common/gristTypes"; import {Sort} from 'app/common/SortSpec'; import {dom, DomElementArg, styled} from 'grainjs'; -import {isListType, RecalcWhen} from "app/common/gristTypes"; -import {ColumnRec} from "app/client/models/entities/ColumnRec"; import * as weasel from 'popweasel'; +import * as commands from "../components/commands"; import isEqual = require('lodash/isEqual'); const t = makeT('GridViewMenus'); @@ -31,12 +35,7 @@ const t = makeT('GridViewMenus'); export function buildAddColumnMenu(gridView: GridView, index?: number) { const isSummaryTable = Boolean(gridView.viewSection.table().summarySourceTable()); return [ - menuItem( - async () => { await gridView.insertColumn(null, {index}); }, - menuIcon('Plus'), - t("Add Column"), - testId('new-columns-menu-add-new'), - ), + buildAddNewColumMenuSection(gridView, index), buildHiddenColumnsMenuItems(gridView, index), isSummaryTable ? null : [ buildLookupSection(gridView, index), @@ -45,6 +44,87 @@ export function buildAddColumnMenu(gridView: GridView, index?: number) { ]; } +function buildAddNewColumMenuSection(gridView: GridView, index?: number): DomElementArg[] { + function buildEmptyNewColumMenuItem() { + return menuItem( + async () => { + await gridView.insertColumn(null, {index}); + }, + t("Add Column"), + testId('new-columns-menu-add-new'), + ); + } + + function BuildNewColumnWithTypeSubmenu() { + const columnTypes = [ + "Text", + "Numeric", + "Int", + "Bool", + "Date", + `DateTime:${gridView.gristDoc.docModel.docInfoRow.timezone()}`, + "Choice", + "ChoiceList", + `Ref:${gridView.tableModel.tableMetaRow.tableId()}`, + `RefList:${gridView.tableModel.tableMetaRow.tableId()}`, + "Attachments"].map(type => ({type, obj: UserType.typeDefs[type.split(':')[0]]})) + .map((ct): { displayName: string, colType: string, testIdName: string, icon: IconName | undefined } => ({ + displayName: t(ct.obj.label), + colType: ct.type, + testIdName: ct.obj.label.toLowerCase().replace(' ', '-'), + icon: ct.obj.icon + })); + + return menuItemSubmenu( + (ctl) => [ + ...columnTypes.map((colType) => + menuItem( + async () => { + await gridView.insertColumn(null, {index, colInfo: {type: colType.colType}}); + }, + menuIcon(colType.icon as IconName), + colType.displayName === 'Reference'? + gridView.gristDoc.behavioralPromptsManager.attachTip('referenceColumns', { + popupOptions: { + attach: `.${menuCssClass}`, + placement: 'left-start', + } + }):null, + colType.displayName, + testId(`new-columns-menu-add-${colType.testIdName}`)), + ), + testId('new-columns-menu-add-with-type-submenu'), + ], + {allowNothingSelected: false}, + t('Add column with type'), + testId('new-columns-menu-add-with-type') + ); + } + + function buildNewFunctionColumnMenuItem() { + return menuItem( + async () => { + await gridView.insertColumn(null, {index, skipPopup: true, colInfo: {isFormula: true}}); + gridView.activateEditorAtCursor(); + commands.allCommands.makeFormula.run(); + commands.allCommands.detachEditor.run(); + }, + withInfoTooltip( + t('Add formula column'), + GristTooltips.formulaColumn(), + {variant: 'hover'} + ), + testId('new-columns-menu-add-formula'), + ); + } + + return [ + buildEmptyNewColumMenuItem(), + BuildNewColumnWithTypeSubmenu(), + buildNewFunctionColumnMenuItem() + ]; +} + function buildHiddenColumnsMenuItems(gridView: GridView, index?: number) { const {viewSection} = gridView; const hiddenColumns = viewSection.hiddenColumns(); diff --git a/app/client/ui/GristTooltips.ts b/app/client/ui/GristTooltips.ts index 8812ae29..48970d81 100644 --- a/app/client/ui/GristTooltips.ts +++ b/app/client/ui/GristTooltips.ts @@ -37,7 +37,8 @@ export type Tooltip = | 'addRowConditionalStyle' | 'addColumnConditionalStyle' | 'uuid' - | 'lookups'; + | 'lookups' + | 'formulaColumn' export type TooltipContentFunc = (...domArgs: DomElementArg[]) => DomContents; @@ -115,6 +116,13 @@ see or edit which parts of your document.') ), ...args, ), + formulaColumn: (...args: DomElementArg[]) => cssTooltipContent( + dom('div', t('Formulas support many Excel functions, full Python syntax, and include a helpful AI Assistant.')), + dom('div', + cssLink({href: commonUrls.formulas, target: '_blank'}, t('Learn more.')), + ), + ...args, + ), }; export interface BehavioralPromptContent { diff --git a/app/common/gristUrls.ts b/app/common/gristUrls.ts index cbc479ed..16bd12f4 100644 --- a/app/common/gristUrls.ts +++ b/app/common/gristUrls.ts @@ -88,6 +88,7 @@ export const commonUrls = { community: 'https://community.getgrist.com', functions: 'https://support.getgrist.com/functions', formulaSheet: 'https://support.getgrist.com/formula-cheat-sheet', + formulas: 'https://support.getgrist.com/formulas', basicTutorial: 'https://templates.getgrist.com/woXtXUBmiN5T/Grist-Basics', basicTutorialImage: 'https://www.getgrist.com/wp-content/uploads/2021/08/lightweight-crm.png', diff --git a/test/nbrowser/GridViewNewColumnMenu.ts b/test/nbrowser/GridViewNewColumnMenu.ts index a97e9aff..bc3662db 100644 --- a/test/nbrowser/GridViewNewColumnMenu.ts +++ b/test/nbrowser/GridViewNewColumnMenu.ts @@ -7,11 +7,13 @@ import {UserAPIImpl} from 'app/common/UserAPI'; describe('GridViewNewColumnMenu', function () { this.timeout('2m'); const cleanup = setupTestSuite(); + gu.bigScreen(); let api: UserAPIImpl; let docId: string; + let session: gu.Session; before(async function () { - const session = await gu.session().login(); + session = await gu.session().login({showTips:true}); api = session.createHomeApi(); docId = await session.tempNewDoc(cleanup, 'ColumnMenu'); @@ -142,6 +144,140 @@ describe('GridViewNewColumnMenu', function () { }); }); + describe('create column with type', function () { + revertThis(); + it('should show "Add Column With type" option', async function () { + // open add new colum menu + await clickAddColumn(); + // check if "Add Column With type" option is persent + const addWithType = await driver.findWait( + '.test-new-columns-menu-add-with-type', + 100, + 'Add Column With Type is not present'); + assert.equal(await addWithType.getText(), 'Add column with type'); + }); + + it('should display reference column popup when opened for the first time', async function(){ + // open add new colum menu + await clickAddColumn(); + // select "Add Column With type" option + await driver.findWait('.test-new-columns-menu-add-with-type', 100).click(); + // wait for submenu to appear + await driver.findWait('.test-new-columns-menu-add-with-type-submenu', 100); + // check if popup is showed + await driver.findWait('.test-behavioral-prompt', 100, 'Reference column popup is not present'); + // close popup + await gu.dismissBehavioralPrompts(); + // close menu + await closeAddColumnMenu(); + // open it again + await clickAddColumn(); + await driver.findWait('.test-new-columns-menu-add-with-type', 100).click(); + await driver.findWait('.test-new-columns-menu-add-with-type-submenu', 100); + // popup should not be showed + assert.isFalse(await driver.find('.test-behavioral-prompt').isPresent()); + await gu.disableTips(session.email); + await closeAddColumnMenu(); + }); + + const optionsToBeDisplayed = [ + "Text", + "Numeric", + "Integer", + "Toggle", + "Date", + "DateTime", + "Choice", + "Choice List", + "Reference", + "Reference List", + "Attachment", + ].map((option) => ({type:option, testClass: option.toLowerCase().replace(' ', '-')})); + for (const option of optionsToBeDisplayed) { + it(`should allow to select column type ${option.type}`, async function () { + // open add new colum menu + await clickAddColumn(); + // select "Add Column With type" option + await driver.findWait('.test-new-columns-menu-add-with-type', 100).click(); + // wait for submenu to appear + await driver.findWait('.test-new-columns-menu-add-with-type-submenu', 100); + // check if it is present in the menu + const element = await driver.findWait( + `.test-new-columns-menu-add-${option.testClass}`.toLowerCase(), + 100, + `${option.type} option is not present`); + // click on the option and check if column is added with a proper type + await element.click(); + //discard rename menu + await driver.findWait('.test-column-title-close', 100).click(); + //check if new column is present + await gu.waitForServer(); + await gu.selectColumn('D'); + await gu.openColumnPanel(); + const type = await gu.getType(); + assert.equal(type, option.type); + + await gu.undo(1); + }); + } + }); + + describe('create formula column', function(){ + revertThis(); + it('should show "create formula column" option with tooltip', async function () { + // open add new colum menu + await clickAddColumn(); + // check if "create formula column" option is present + const addWithType = await driver.findWait('.test-new-columns-menu-add-formula', 100, + 'Add formula column is not present'); + // check if it has a tooltip button + const tooltip = await addWithType.findWait('.test-info-tooltip', 100, + 'Tooltip button is not present'); + // check if tooltip is show after hovering + await tooltip.mouseMove(); + const tooltipContainer = await driver.findWait('.test-info-tooltip-popup', + 100, + 'Tooltip is not shown'); + // check if tooltip is showing valid message + const tooltipText = await tooltipContainer.getText(); + assert.include(tooltipText, + 'Formulas support many Excel functions, full Python syntax, and include a helpful AI Assistant.', + 'Tooltip is showing wrong message'); + // check if link in tooltip has a proper href + const hrefAddress = await tooltipContainer.findWait('a', + 100, + 'Tooltip link is not present'); + assert.equal(await hrefAddress.getText(), 'Learn more.'); + assert.equal(await hrefAddress.getAttribute('href'), + 'https://support.getgrist.com/formulas', + 'Tooltip link has wrong href'); + }); + + it('should allow to select formula column', async function () { + // open column panel - we will need it later + await gu.openColumnPanel(); + // open add new colum menu + await clickAddColumn(); + // select "create formula column" option + await driver.findWait('.test-new-columns-menu-add-formula', 100).click(); + // there should not be a rename poup + assert.isFalse(await driver.find('test-column-title-popup').isPresent()); + //check if new column is present + await gu.waitForServer(); + // check if editor popup is opened + await driver.findWait('.test-floating-editor-popup', 200, 'Editor popup is not present'); + // write some formula + await gu.sendKeys('1+1'); + await driver.find('.test-formula-editor-save-button').click(); + await gu.waitForServer(); + // check if column is created with a proper type + const type = await gu.columnBehavior(); + assert.equal(type, 'Formula Column'); + + }); + }); + + describe('hidden columns', function () { revertThis();