(core) "add column with type" and "add formula column" options in new column menu

Summary: New Column menu was enhanced by "add column with type" and "add formula column" options. First one allow user to chose the type of newly created column, to save time for selecting this option in creator menu. "Add formula column" opens formula editor in popup state right after creating the column. In this case, renaming column popup is ignored to not overburden user with to many popup at once.

Test Plan: new nbrowser test was added to check validity of menu items, and output of menu action - if columns have given types or if formula editor popup is opened and functionin, accordingly.

Reviewers: georgegevoian

Differential Revision: https://phab.getgrist.com/D4113
This commit is contained in:
Jakub Serafin 2023-11-22 11:02:04 +01:00
parent 707a8c7b32
commit c04f61738c
4 changed files with 235 additions and 10 deletions

View File

@ -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();

View File

@ -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 {

View File

@ -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',

View File

@ -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();