mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) Implementing search on raw data view
Summary:
Search now works on Raw Data Page.
- Search bar option 'Search on all pages' will change to 'Search on all tables' when on the Raw data page, and will allow searching through all tables.
- Little CSS adjustment for an overlay on Raw page (removes z-index as it is not needed, and conflicts with searchbar).
- Search bar option ('search on all') gets white background, little padding, and is moved 2 pixels up, this is needed for Raw page.
Test Plan: new and updated tests
Reviewers: georgegevoian
Reviewed By: georgegevoian
Differential Revision: https://phab.getgrist.com/D3376
			
			
This commit is contained in:
		
							parent
							
								
									007a862333
								
							
						
					
					
						commit
						dea1a8ba1b
					
				@ -49,7 +49,10 @@ export class DataTables extends Disposable {
 | 
			
		||||
            cssItem(
 | 
			
		||||
              testId('table'),
 | 
			
		||||
              cssItemContent(
 | 
			
		||||
                cssIcon('TypeTable'),
 | 
			
		||||
                cssIcon('TypeTable',
 | 
			
		||||
                  // Element to click in tests.
 | 
			
		||||
                  dom.domComputed(use => `table-id-${use(tableRec.tableId)}`)
 | 
			
		||||
                ),
 | 
			
		||||
                cssLabels(
 | 
			
		||||
                  cssTitleLine(
 | 
			
		||||
                    cssLine(
 | 
			
		||||
 | 
			
		||||
@ -6,12 +6,13 @@ import {printViewSection} from 'app/client/components/Printing';
 | 
			
		||||
import {buildViewSectionDom, ViewSectionHelper} from 'app/client/components/ViewLayout';
 | 
			
		||||
import {colors, mediaSmall, vars} from 'app/client/ui2018/cssVars';
 | 
			
		||||
import {icon} from 'app/client/ui2018/icons';
 | 
			
		||||
import {Disposable, dom, fromKo, makeTestId, styled} from 'grainjs';
 | 
			
		||||
import {Computed, Disposable, dom, fromKo, makeTestId, Observable, styled} from 'grainjs';
 | 
			
		||||
import {reportError} from 'app/client/models/errors';
 | 
			
		||||
 | 
			
		||||
const testId = makeTestId('test-raw-data-');
 | 
			
		||||
 | 
			
		||||
export class RawData extends Disposable {
 | 
			
		||||
  private _lightboxVisible: Observable<boolean>;
 | 
			
		||||
  constructor(private _gristDoc: GristDoc) {
 | 
			
		||||
    super();
 | 
			
		||||
    const commandGroup = {
 | 
			
		||||
@ -19,6 +20,10 @@ export class RawData extends Disposable {
 | 
			
		||||
      printSection: () => { printViewSection(null, this._gristDoc.viewModel.activeSection()).catch(reportError); },
 | 
			
		||||
    };
 | 
			
		||||
    this.autoDispose(commands.createGroup(commandGroup, this, true));
 | 
			
		||||
    this._lightboxVisible = Computed.create(this, use => {
 | 
			
		||||
      const section = use(this._gristDoc.viewModel.activeSection);
 | 
			
		||||
      return Boolean(section.getRowId());
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public buildDom() {
 | 
			
		||||
@ -26,8 +31,12 @@ export class RawData extends Disposable {
 | 
			
		||||
    const close = this._close.bind(this);
 | 
			
		||||
 | 
			
		||||
    return cssContainer(
 | 
			
		||||
      dom.create(DataTables, this._gristDoc),
 | 
			
		||||
      dom.create(DocumentUsage, this._gristDoc.docPageModel),
 | 
			
		||||
      dom('div',
 | 
			
		||||
        dom.create(DataTables, this._gristDoc),
 | 
			
		||||
        dom.create(DocumentUsage, this._gristDoc.docPageModel),
 | 
			
		||||
        // We are hiding it, because overlay doesn't have a z-index (it conflicts with a searchbar and list buttons)
 | 
			
		||||
        dom.hide(this._lightboxVisible)
 | 
			
		||||
      ),
 | 
			
		||||
      /***************  Lightbox section **********/
 | 
			
		||||
      dom.domComputedOwned(fromKo(this._gristDoc.viewModel.activeSection), (owner, viewSection) => {
 | 
			
		||||
        if (!viewSection.getRowId()) {
 | 
			
		||||
@ -80,7 +89,6 @@ const cssContainer = styled('div', `
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
const cssOverlay = styled('div', `
 | 
			
		||||
  z-index: 10;
 | 
			
		||||
  background-color: ${colors.backdrop};
 | 
			
		||||
  inset: 0px;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
 | 
			
		||||
@ -3,13 +3,14 @@
 | 
			
		||||
 | 
			
		||||
import {CursorPos} from 'app/client/components/Cursor';
 | 
			
		||||
import {GristDoc} from 'app/client/components/GristDoc';
 | 
			
		||||
import {ViewFieldRec, ViewSectionRec} from 'app/client/models/DocModel';
 | 
			
		||||
import {PageRec, ViewFieldRec, ViewSectionRec} from 'app/client/models/DocModel';
 | 
			
		||||
import {reportError} from 'app/client/models/errors';
 | 
			
		||||
import {delay} from 'app/common/delay';
 | 
			
		||||
import {waitObs} from 'app/common/gutil';
 | 
			
		||||
import {IDocPage} from 'app/common/gristUrls';
 | 
			
		||||
import {nativeCompare, waitObs} from 'app/common/gutil';
 | 
			
		||||
import {TableData} from 'app/common/TableData';
 | 
			
		||||
import {BaseFormatter} from 'app/common/ValueFormatter';
 | 
			
		||||
import {Disposable, Observable} from 'grainjs';
 | 
			
		||||
import {Computed, Disposable, Observable} from 'grainjs';
 | 
			
		||||
import debounce = require('lodash/debounce');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -22,6 +23,7 @@ export interface SearchModel {
 | 
			
		||||
  isEmpty: Observable<boolean>;     // indicates whether the value is empty
 | 
			
		||||
  isRunning: Observable<boolean>;  // indicates that matching is in progress
 | 
			
		||||
  multiPage: Observable<boolean>;   // if true will search across all pages
 | 
			
		||||
  allLabel: Observable<string>;   // label to show instead of default 'Search all pages'
 | 
			
		||||
 | 
			
		||||
  findNext(): Promise<void>;       // find next match
 | 
			
		||||
  findPrev(): Promise<void>;       // find previous match
 | 
			
		||||
@ -86,7 +88,63 @@ interface IFinder {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A callback to opening a page: useful to switch to next page during an ongoing search.
 | 
			
		||||
type DocPageOpener = (viewId: number) => Promise<void>;
 | 
			
		||||
type DocPageOpener = (viewId: IDocPage) => Promise<void>;
 | 
			
		||||
 | 
			
		||||
// To support Raw Data Views we will introduce a 'wrapped' page abstraction. Raw data
 | 
			
		||||
// page is not a true page (it doesn't have a record), this will allow as to treat a raw view section
 | 
			
		||||
// as if it were a PageRec.
 | 
			
		||||
interface ISearchablePageRec {
 | 
			
		||||
  viewSections(): ViewSectionRec[];
 | 
			
		||||
  activeSectionId(): number;
 | 
			
		||||
  getViewId(): IDocPage;
 | 
			
		||||
  openPage(): Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class RawSectionWrapper implements ISearchablePageRec {
 | 
			
		||||
  constructor(private _section: ViewSectionRec) {
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
  public viewSections(): ViewSectionRec[] {
 | 
			
		||||
    return [this._section];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public activeSectionId() {
 | 
			
		||||
    return this._section.id.peek();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getViewId(): IDocPage {
 | 
			
		||||
    return 'data';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async openPage() {
 | 
			
		||||
    this._section.view.peek().activeSectionId(this._section.getRowId());
 | 
			
		||||
    await waitObs(this._section.viewInstance);
 | 
			
		||||
    await this._section.viewInstance.peek()?.getLoadingDonePromise();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class PageRecWrapper implements ISearchablePageRec {
 | 
			
		||||
  constructor(private _page: PageRec, private _opener: DocPageOpener) {
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
  public viewSections(): ViewSectionRec[] {
 | 
			
		||||
    return this._page.view.peek().viewSections.peek().peek();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public activeSectionId() {
 | 
			
		||||
    return this._page.view.peek().activeSectionId.peek();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getViewId() {
 | 
			
		||||
    return this._page.view.peek().getRowId();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public openPage() {
 | 
			
		||||
    return this._opener(this.getViewId());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//activeSectionId
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An implementation of an IFinder.
 | 
			
		||||
@ -96,7 +154,7 @@ class FinderImpl implements IFinder {
 | 
			
		||||
  public startPosition: SearchPosition;
 | 
			
		||||
 | 
			
		||||
  private _searchRegexp: RegExp;
 | 
			
		||||
  private _pageStepper = new Stepper<any>();
 | 
			
		||||
  private _pageStepper = new Stepper<ISearchablePageRec>();
 | 
			
		||||
  private _sectionStepper = new Stepper<ViewSectionRec>();
 | 
			
		||||
  private _sectionTableData: TableData;
 | 
			
		||||
  private _rowStepper = new Stepper<number>();
 | 
			
		||||
@ -126,16 +184,40 @@ class FinderImpl implements IFinder {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Initialize the steppers. Returns false if anything goes wrong.
 | 
			
		||||
  public init(): boolean {
 | 
			
		||||
    const pages: any[] = this._gristDoc.docModel.visibleDocPages.peek();
 | 
			
		||||
    this._pageStepper.array = pages;
 | 
			
		||||
    this._pageStepper.index = pages.findIndex(t => t.viewRef() === this._gristDoc.activeViewId.get());
 | 
			
		||||
    if (this._pageStepper.index < 0) { return false; }
 | 
			
		||||
  public async init(): Promise<boolean> {
 | 
			
		||||
    // If we are on a raw view page, pretend that we are looking at true pages.
 | 
			
		||||
    if ('data' === this._gristDoc.activeViewId.get()) {
 | 
			
		||||
      // Get all raw sections.
 | 
			
		||||
      const rawSections = this._gristDoc.docModel.allTables.peek()
 | 
			
		||||
                              // Filter out those we don't have permissions to see (through ACL-tableId will be empty).
 | 
			
		||||
                              .filter(t => Boolean(t.tableId.peek()))
 | 
			
		||||
                              // sort in order that is the same as on the raw data list page,
 | 
			
		||||
                              .sort((a, b) => nativeCompare(a.tableTitle.peek(), b.tableTitle.peek()))
 | 
			
		||||
                              // get rawViewSection,
 | 
			
		||||
                              .map(t => t.rawViewSection.peek())
 | 
			
		||||
                              // and test if it isn't an empty record.
 | 
			
		||||
                              .filter(s => Boolean(s.id.peek()));
 | 
			
		||||
      // Pretend that those are pages.
 | 
			
		||||
      this._pageStepper.array = rawSections.map(r => new RawSectionWrapper(r));
 | 
			
		||||
      // Find currently selected one (by comparing to active section id)
 | 
			
		||||
      this._pageStepper.index = rawSections.findIndex(s =>
 | 
			
		||||
        s.getRowId() === this._gristDoc.viewModel.activeSectionId.peek());
 | 
			
		||||
      // If we are at listing, where no section is active open the first page. Otherwise, search will fail.
 | 
			
		||||
      if (this._pageStepper.index < 0) {
 | 
			
		||||
        this._pageStepper.index = 0;
 | 
			
		||||
        await this._pageStepper.value.openPage();
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // Else read all visible pages.
 | 
			
		||||
      const pages = this._gristDoc.docModel.visibleDocPages.peek();
 | 
			
		||||
      this._pageStepper.array = pages.map(p => new PageRecWrapper(p, this._openDocPageCB));
 | 
			
		||||
      this._pageStepper.index = pages.findIndex(t => t.viewRef.peek() === this._gristDoc.activeViewId.get());
 | 
			
		||||
      if (this._pageStepper.index < 0) { return false; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const view = this._pageStepper.value.view.peek();
 | 
			
		||||
    const sections: any[] = view.viewSections().peek();
 | 
			
		||||
    const sections = this._pageStepper.value.viewSections();
 | 
			
		||||
    this._sectionStepper.array = sections;
 | 
			
		||||
    this._sectionStepper.index = sections.findIndex(s => s.getRowId() === view.activeSectionId());
 | 
			
		||||
    this._sectionStepper.index = sections.findIndex(s => s.getRowId() === this._pageStepper.value.activeSectionId());
 | 
			
		||||
    if (this._sectionStepper.index < 0) { return false; }
 | 
			
		||||
 | 
			
		||||
    this._initNewSectionShown();
 | 
			
		||||
@ -248,8 +330,8 @@ class FinderImpl implements IFinder {
 | 
			
		||||
    await this._pageStepper.next(step, () => undefined);
 | 
			
		||||
    this._pagesSwitched++;
 | 
			
		||||
 | 
			
		||||
    const view = this._pageStepper.value.view.peek();
 | 
			
		||||
    this._sectionStepper.array = view.viewSections().peek();
 | 
			
		||||
    const view = this._pageStepper.value;
 | 
			
		||||
    this._sectionStepper.array = view.viewSections();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _initFormatters() {
 | 
			
		||||
@ -287,15 +369,16 @@ class FinderImpl implements IFinder {
 | 
			
		||||
    // viewInstance to be created, reset the section info, and return true to continue searching.
 | 
			
		||||
    const section = this._sectionStepper.value;
 | 
			
		||||
    if (!section.viewInstance.peek()) {
 | 
			
		||||
      const view = this._pageStepper.value.view.peek();
 | 
			
		||||
      await this._openDocPage(view.getRowId());
 | 
			
		||||
      console.log("SearchBar: loading view %s section %s", view.getRowId(), section.getRowId());
 | 
			
		||||
      const view = this._pageStepper.value;
 | 
			
		||||
      if (this._aborted) { return false; }
 | 
			
		||||
      await view.openPage();
 | 
			
		||||
      console.log("SearchBar: loading view %s section %s", view.getViewId(), section.getRowId());
 | 
			
		||||
      const viewInstance: any = await waitObs(section.viewInstance);
 | 
			
		||||
      await viewInstance.getLoadingDonePromise();
 | 
			
		||||
      this._initNewSectionShown();
 | 
			
		||||
      this._rowStepper.setStart(step);
 | 
			
		||||
      this._fieldStepper.setStart(step);
 | 
			
		||||
      console.log("SearchBar: loaded view %s section %s", view.getRowId(), section.getRowId());
 | 
			
		||||
      console.log("SearchBar: loaded view %s section %s", view.getViewId(), section.getRowId());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
@ -346,11 +429,6 @@ class FinderImpl implements IFinder {
 | 
			
		||||
        this._fieldStepper.index === pos.fieldIndex
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _openDocPage(viewId: number) {
 | 
			
		||||
    if (this._aborted) { return; }
 | 
			
		||||
    return this._openDocPageCB(viewId);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -363,6 +441,7 @@ export class SearchModelImpl extends Disposable implements SearchModel {
 | 
			
		||||
  public readonly noMatch = Observable.create(this, true);
 | 
			
		||||
  public readonly isEmpty = Observable.create(this, true);
 | 
			
		||||
  public readonly multiPage = Observable.create(this, false);
 | 
			
		||||
  public readonly allLabel: Computed<string>;
 | 
			
		||||
 | 
			
		||||
  private _isRestartNeeded = false;
 | 
			
		||||
  private _finder: IFinder|null = null;
 | 
			
		||||
@ -377,12 +456,23 @@ export class SearchModelImpl extends Disposable implements SearchModel {
 | 
			
		||||
    // Set this.noMatch to false when multiPage gets turned ON.
 | 
			
		||||
    this.autoDispose(this.multiPage.addListener(v => { if (v) { this.noMatch.set(false); } }));
 | 
			
		||||
 | 
			
		||||
    this.allLabel = Computed.create(this, use => use(this._gristDoc.activeViewId) === 'data' ?
 | 
			
		||||
        'Search all tables' : 'Search all pages');
 | 
			
		||||
 | 
			
		||||
    // Schedule a search restart when user changes pages (otherwise search would resume from the
 | 
			
		||||
    // previous page that is not shown anymore). Also revert noMatch flag when in single page mode.
 | 
			
		||||
    this.autoDispose(this._gristDoc.activeViewId.addListener(() => {
 | 
			
		||||
      if (!this.multiPage.get()) { this.noMatch.set(false); }
 | 
			
		||||
      this._isRestartNeeded = true;
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    // On Raw data view, whenever table is closed (so activeSectionId = 0), restart search.
 | 
			
		||||
    this.autoDispose(this._gristDoc.viewModel.activeSectionId.subscribe((sectionId) => {
 | 
			
		||||
      if (this._gristDoc.activeViewId.get() === 'data' && sectionId === 0) {
 | 
			
		||||
        this._isRestartNeeded = true;
 | 
			
		||||
        this.noMatch.set(false);
 | 
			
		||||
      }
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async findNext() {
 | 
			
		||||
@ -406,17 +496,17 @@ export class SearchModelImpl extends Disposable implements SearchModel {
 | 
			
		||||
  private async _findFirst(value: string) {
 | 
			
		||||
    this._isRestartNeeded = false;
 | 
			
		||||
    this.isEmpty.set(!value);
 | 
			
		||||
    this._updateFinder(value);
 | 
			
		||||
    await this._updateFinder(value);
 | 
			
		||||
    if (!value || !this._finder) { this.noMatch.set(true); return; }
 | 
			
		||||
    await this._run(async (finder) => {
 | 
			
		||||
      await finder.matchNext(1);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _updateFinder(value: string) {
 | 
			
		||||
  private async _updateFinder(value: string) {
 | 
			
		||||
    if (this._finder) { this._finder.abort(); }
 | 
			
		||||
    const impl = new FinderImpl(this._gristDoc, value, this._openDocPage.bind(this), this.multiPage);
 | 
			
		||||
    const isValid = impl.init();
 | 
			
		||||
    const isValid = await impl.init();
 | 
			
		||||
    this._finder = isValid ? impl : null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -438,7 +528,7 @@ export class SearchModelImpl extends Disposable implements SearchModel {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Opens doc page without triggering a restart.
 | 
			
		||||
  private async _openDocPage(viewId: number) {
 | 
			
		||||
  private async _openDocPage(viewId: IDocPage) {
 | 
			
		||||
    await this._gristDoc.openDocPage(viewId);
 | 
			
		||||
    this._isRestartNeeded = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,8 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse
 | 
			
		||||
                           gristDoc.docModel.rules.getNumRows() > 0);
 | 
			
		||||
  }
 | 
			
		||||
  owner.autoDispose(gristDoc.docModel.rules.tableData.tableActionEmitter.addListener(updateCanViewAccessRules));
 | 
			
		||||
  // TODO: Create global observable to enable raw tools (TO REMOVE once raw data ui has landed)
 | 
			
		||||
  (window as any).enableRawTools = Observable.create(null, false);
 | 
			
		||||
  updateCanViewAccessRules();
 | 
			
		||||
  return cssTools(
 | 
			
		||||
    cssTools.cls('-collapsed', (use) => !use(leftPanelOpen)),
 | 
			
		||||
@ -47,15 +49,16 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse
 | 
			
		||||
      testId('access-rules'),
 | 
			
		||||
    ),
 | 
			
		||||
    // Raw data - for now hidden.
 | 
			
		||||
    // cssPageEntry(
 | 
			
		||||
    //   cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'data'),
 | 
			
		||||
    //   cssPageLink(
 | 
			
		||||
    //     cssPageIcon('Database'),
 | 
			
		||||
    //     cssLinkText('Raw data'),
 | 
			
		||||
    //     testId('raw'),
 | 
			
		||||
    //     urlState().setLinkUrl({docPage: 'data'})
 | 
			
		||||
    //   )
 | 
			
		||||
    // ),
 | 
			
		||||
    dom.maybe((window as any).enableRawTools, () =>
 | 
			
		||||
      cssPageEntry(
 | 
			
		||||
        cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'data'),
 | 
			
		||||
        cssPageLink(
 | 
			
		||||
          cssPageIcon('Database'),
 | 
			
		||||
          cssLinkText('Raw data'),
 | 
			
		||||
          testId('raw'),
 | 
			
		||||
          urlState().setLinkUrl({docPage: 'data'})
 | 
			
		||||
        ),
 | 
			
		||||
    )),
 | 
			
		||||
    cssPageEntry(
 | 
			
		||||
      cssPageLink(cssPageIcon('Log'), cssLinkText('Document History'), testId('log'),
 | 
			
		||||
        dom.on('click', () => gristDoc.showTool('docHistory')))
 | 
			
		||||
 | 
			
		||||
@ -109,8 +109,12 @@ const cssLabel = styled('span', `
 | 
			
		||||
const cssOptions = styled('div', `
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  top: 48px;
 | 
			
		||||
  top: 46px;
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
  background: white;
 | 
			
		||||
  padding: 2px 4px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
`);
 | 
			
		||||
 | 
			
		||||
const cssShortcut = styled('span', `
 | 
			
		||||
@ -202,7 +206,7 @@ export function searchBar(model: SearchModel, testId: TestId = noTestId) {
 | 
			
		||||
        testId('close'),
 | 
			
		||||
        dom.on('click', () => toggleMenu(false))),
 | 
			
		||||
      cssOptions(
 | 
			
		||||
        labeledSquareCheckbox(model.multiPage, 'Search all pages'),
 | 
			
		||||
        labeledSquareCheckbox(model.multiPage, dom.text(model.allLabel)),
 | 
			
		||||
        dom.on('mouseenter', () => keepExpanded = true),
 | 
			
		||||
        dom.on('mouseleave', () => keepExpanded = false),
 | 
			
		||||
        testId('option-all-pages'),
 | 
			
		||||
 | 
			
		||||
@ -1045,15 +1045,95 @@ export async function moveToHidden(col: string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function search(what: string) {
 | 
			
		||||
  await driver.find('.test-tb-search-icon').doClick();
 | 
			
		||||
  await driver.find('.test-tb-search-icon').click();
 | 
			
		||||
  await driver.sleep(500);
 | 
			
		||||
  await driver.find('.test-tb-search-input').doClick();
 | 
			
		||||
  await driver.find('.test-tb-search-input input').click();
 | 
			
		||||
  await selectAll();
 | 
			
		||||
  await driver.sendKeys(what);
 | 
			
		||||
  // Sleep for search debounce time
 | 
			
		||||
  await driver.sleep(120);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function toggleSearchAll() {
 | 
			
		||||
  await closeTooltip();
 | 
			
		||||
  await driver.find('.test-tb-search-option-all-pages').click();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function closeSearch() {
 | 
			
		||||
  await driver.sendKeys(Key.ESCAPE);
 | 
			
		||||
  await driver.sleep(500);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function closeTooltip() {
 | 
			
		||||
  await driver.mouseMoveBy({x : 100, y: 100});
 | 
			
		||||
  await waitToPass(async () => {
 | 
			
		||||
    assert.equal(await driver.find('.test-tooltip').isPresent(), false);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function searchNext() {
 | 
			
		||||
  await closeTooltip();
 | 
			
		||||
  await driver.find('.test-tb-search-next').click();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function searchPrev() {
 | 
			
		||||
  await closeTooltip();
 | 
			
		||||
  await driver.find('.test-tb-search-prev').click();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getCurrentSectionName() {
 | 
			
		||||
  return driver.find('.active_section .test-viewsection-title').value();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getCurrentPageName() {
 | 
			
		||||
  return driver.find('.test-treeview-itemHeader.selected').find('.test-docpage-label').getText();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getActiveRawTableName() {
 | 
			
		||||
  const title = await driver.findWait('.test-raw-data-overlay .test-viewsection-title', 100).value();
 | 
			
		||||
  return title;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getSearchInput() {
 | 
			
		||||
  return driver.find('.test-tb-search-input');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function hasNoResult() {
 | 
			
		||||
  await waitToPass(async () => {
 | 
			
		||||
    assert.match(await driver.find('.test-tb-search-input').getText(), /No results/);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function hasSomeResult() {
 | 
			
		||||
  await waitToPass(async () => {
 | 
			
		||||
    assert.notMatch(await driver.find('.test-tb-search-input').getText(), /No results/);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function searchIsOpened() {
 | 
			
		||||
  await waitToPass(async () => {
 | 
			
		||||
    assert.isAbove((await getSearchInput().rect()).width, 50);
 | 
			
		||||
  }, 500);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function searchIsClosed() {
 | 
			
		||||
  await waitToPass(async () => {
 | 
			
		||||
    assert.equal((await getSearchInput().rect()).width, 0);
 | 
			
		||||
  }, 500);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function openRawTable(tableId: string) {
 | 
			
		||||
  await driver.find(`.test-raw-data-table .test-raw-data-table-id-${tableId}`).click();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function isRawTableOpened() {
 | 
			
		||||
  return await driver.find('.test-raw-data-close-button').isPresent();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function closeRawTable() {
 | 
			
		||||
  await driver.find('.test-raw-data-close-button').click();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Toggles (opens or closes) the filter bar for a section.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user