mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) Keep track of row counts per table
Summary: Displays a live row count of each table on the Raw Data page. Test Plan: Browser tests. Reviewers: alexmojaki Reviewed By: alexmojaki Differential Revision: https://phab.getgrist.com/D3540
This commit is contained in:
		
							parent
							
								
									40c9b8b7e8
								
							
						
					
					
						commit
						771e1edd54
					
				@ -1,12 +1,10 @@
 | 
				
			|||||||
import {GristDoc} from 'app/client/components/GristDoc';
 | 
					import {GristDoc} from 'app/client/components/GristDoc';
 | 
				
			||||||
import {copyToClipboard} from 'app/client/lib/copyToClipboard';
 | 
					import {copyToClipboard} from 'app/client/lib/copyToClipboard';
 | 
				
			||||||
import {localStorageObs} from 'app/client/lib/localStorageObs';
 | 
					 | 
				
			||||||
import {setTestState} from 'app/client/lib/testState';
 | 
					import {setTestState} from 'app/client/lib/testState';
 | 
				
			||||||
import {TableRec} from 'app/client/models/DocModel';
 | 
					import {TableRec} from 'app/client/models/DocModel';
 | 
				
			||||||
import {docListHeader, docMenuTrigger} from 'app/client/ui/DocMenuCss';
 | 
					import {docListHeader, docMenuTrigger} from 'app/client/ui/DocMenuCss';
 | 
				
			||||||
import {showTransientTooltip} from 'app/client/ui/tooltips';
 | 
					import {showTransientTooltip} from 'app/client/ui/tooltips';
 | 
				
			||||||
import {buildTableName} from 'app/client/ui/WidgetTitle';
 | 
					import {buildTableName} from 'app/client/ui/WidgetTitle';
 | 
				
			||||||
import {buttonSelect, cssButtonSelect} from 'app/client/ui2018/buttonSelect';
 | 
					 | 
				
			||||||
import * as css from 'app/client/ui2018/cssVars';
 | 
					import * as css from 'app/client/ui2018/cssVars';
 | 
				
			||||||
import {icon} from 'app/client/ui2018/icons';
 | 
					import {icon} from 'app/client/ui2018/icons';
 | 
				
			||||||
import {menu, menuItem, menuText} from 'app/client/ui2018/menus';
 | 
					import {menu, menuItem, menuText} from 'app/client/ui2018/menus';
 | 
				
			||||||
@ -17,7 +15,13 @@ const testId = makeTestId('test-raw-data-');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export class DataTables extends Disposable {
 | 
					export class DataTables extends Disposable {
 | 
				
			||||||
  private _tables: Observable<TableRec[]>;
 | 
					  private _tables: Observable<TableRec[]>;
 | 
				
			||||||
  private _view: Observable<string | null>;
 | 
					
 | 
				
			||||||
 | 
					  private readonly _rowCount = Computed.create(
 | 
				
			||||||
 | 
					    this, this._gristDoc.docPageModel.currentDocUsage, (_use, usage) => {
 | 
				
			||||||
 | 
					      return usage?.rowCount;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(private _gristDoc: GristDoc) {
 | 
					  constructor(private _gristDoc: GristDoc) {
 | 
				
			||||||
    super();
 | 
					    super();
 | 
				
			||||||
    this._tables = Computed.create(this, use => {
 | 
					    this._tables = Computed.create(this, use => {
 | 
				
			||||||
@ -26,10 +30,6 @@ export class DataTables extends Disposable {
 | 
				
			|||||||
      // Remove tables that we don't have access to. ACL will remove tableId from those tables.
 | 
					      // Remove tables that we don't have access to. ACL will remove tableId from those tables.
 | 
				
			||||||
      return [...dataTables, ...summaryTables].filter(t => Boolean(use(t.tableId)));
 | 
					      return [...dataTables, ...summaryTables].filter(t => Boolean(use(t.tableId)));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get the user id, to remember selected layout on the next visit.
 | 
					 | 
				
			||||||
    const userId = this._gristDoc.app.topAppModel.appObs.get()?.currentUser?.id ?? 0;
 | 
					 | 
				
			||||||
    this._view = this.autoDispose(localStorageObs(`u=${userId}:raw:viewType`, "list"));
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public buildDom() {
 | 
					  public buildDom() {
 | 
				
			||||||
@ -37,22 +37,8 @@ export class DataTables extends Disposable {
 | 
				
			|||||||
      cssTableList(
 | 
					      cssTableList(
 | 
				
			||||||
        /***************  List section **********/
 | 
					        /***************  List section **********/
 | 
				
			||||||
        testId('list'),
 | 
					        testId('list'),
 | 
				
			||||||
        cssBetween(
 | 
					        docListHeader('Raw Data Tables'),
 | 
				
			||||||
          docListHeader('Raw Data Tables'),
 | 
					 | 
				
			||||||
          cssSwitch(
 | 
					 | 
				
			||||||
            buttonSelect<any>(
 | 
					 | 
				
			||||||
              this._view,
 | 
					 | 
				
			||||||
              [
 | 
					 | 
				
			||||||
                {value: 'card', icon: 'TypeTable'},
 | 
					 | 
				
			||||||
                {value: 'list', icon: 'TypeCardList'},
 | 
					 | 
				
			||||||
              ],
 | 
					 | 
				
			||||||
              css.testId('view-mode'),
 | 
					 | 
				
			||||||
              cssButtonSelect.cls("-light")
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        cssList(
 | 
					        cssList(
 | 
				
			||||||
          cssList.cls(use => `-${use(this._view)}`),
 | 
					 | 
				
			||||||
          dom.forEach(this._tables, tableRec =>
 | 
					          dom.forEach(this._tables, tableRec =>
 | 
				
			||||||
            cssItem(
 | 
					            cssItem(
 | 
				
			||||||
              testId('table'),
 | 
					              testId('table'),
 | 
				
			||||||
@ -63,10 +49,10 @@ export class DataTables extends Disposable {
 | 
				
			|||||||
                )),
 | 
					                )),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              cssMiddle(
 | 
					              cssMiddle(
 | 
				
			||||||
                css60(cssTableTitle(this._tableTitle(tableRec), testId('table-title'))),
 | 
					                cssTitleRow(cssTableTitle(this._tableTitle(tableRec), testId('table-title'))),
 | 
				
			||||||
                css40(
 | 
					                cssDetailsRow(
 | 
				
			||||||
                  cssIdHoverWrapper(
 | 
					                  cssTableIdWrapper(cssHoverWrapper(
 | 
				
			||||||
                    cssUpperCase("Table id: "),
 | 
					                    cssUpperCase("Table ID: "),
 | 
				
			||||||
                    cssTableId(
 | 
					                    cssTableId(
 | 
				
			||||||
                      testId('table-id'),
 | 
					                      testId('table-id'),
 | 
				
			||||||
                      dom.text(tableRec.tableId),
 | 
					                      dom.text(tableRec.tableId),
 | 
				
			||||||
@ -75,13 +61,14 @@ export class DataTables extends Disposable {
 | 
				
			|||||||
                    dom.on('click', async (e, t) => {
 | 
					                    dom.on('click', async (e, t) => {
 | 
				
			||||||
                      e.stopImmediatePropagation();
 | 
					                      e.stopImmediatePropagation();
 | 
				
			||||||
                      e.preventDefault();
 | 
					                      e.preventDefault();
 | 
				
			||||||
                      showTransientTooltip(t, 'Table id copied to clipboard', {
 | 
					                      showTransientTooltip(t, 'Table ID copied to clipboard', {
 | 
				
			||||||
                        key: 'copy-table-id'
 | 
					                        key: 'copy-table-id'
 | 
				
			||||||
                      });
 | 
					                      });
 | 
				
			||||||
                      await copyToClipboard(tableRec.tableId.peek());
 | 
					                      await copyToClipboard(tableRec.tableId.peek());
 | 
				
			||||||
                      setTestState({clipboard: tableRec.tableId.peek()});
 | 
					                      setTestState({clipboard: tableRec.tableId.peek()});
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                  )
 | 
					                  )),
 | 
				
			||||||
 | 
					                  this._tableRows(tableRec),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              cssRight(
 | 
					              cssRight(
 | 
				
			||||||
@ -150,6 +137,21 @@ export class DataTables extends Disposable {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    confirmModal(`Delete ${t.formattedTableName()} data, and remove it from all pages?`, 'Delete', doRemove);
 | 
					    confirmModal(`Delete ${t.formattedTableName()} data, and remove it from all pages?`, 'Delete', doRemove);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private _tableRows(table: TableRec) {
 | 
				
			||||||
 | 
					    return cssTableRowsWrapper(
 | 
				
			||||||
 | 
					      cssUpperCase("Rows: "),
 | 
				
			||||||
 | 
					      cssTableRows(
 | 
				
			||||||
 | 
					        testId('table-rows'),
 | 
				
			||||||
 | 
					        dom.text(use => {
 | 
				
			||||||
 | 
					          const rowCounts = use(this._rowCount);
 | 
				
			||||||
 | 
					          if (typeof rowCounts !== 'object') { return ''; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return rowCounts[table.getRowId()]?.toString() ?? '';
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const container = styled('div', `
 | 
					const container = styled('div', `
 | 
				
			||||||
@ -157,38 +159,10 @@ const container = styled('div', `
 | 
				
			|||||||
  position: relative;
 | 
					  position: relative;
 | 
				
			||||||
`);
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cssBetween = styled('div', `
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  justify-content: space-between;
 | 
					 | 
				
			||||||
`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Below styles makes the list view look like a card view
 | 
					 | 
				
			||||||
// on smaller screens.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const cssSwitch = styled('div', `
 | 
					 | 
				
			||||||
  @media ${css.mediaXSmall} {
 | 
					 | 
				
			||||||
    & {
 | 
					 | 
				
			||||||
      display: none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const cssList = styled('div', `
 | 
					const cssList = styled('div', `
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  &-list {
 | 
					  flex-direction: column;
 | 
				
			||||||
    flex-direction: column;
 | 
					  gap: 12px;
 | 
				
			||||||
    gap: 8px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  &-card {
 | 
					 | 
				
			||||||
    flex-direction: row;
 | 
					 | 
				
			||||||
    flex-wrap: wrap;
 | 
					 | 
				
			||||||
    gap: 24px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  @media ${css.mediaSmall} {
 | 
					 | 
				
			||||||
    & {
 | 
					 | 
				
			||||||
      gap: 12px !important;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
`);
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cssItem = styled('div', `
 | 
					const cssItem = styled('div', `
 | 
				
			||||||
@ -196,29 +170,13 @@ const cssItem = styled('div', `
 | 
				
			|||||||
  align-items: center;
 | 
					  align-items: center;
 | 
				
			||||||
  cursor: pointer;
 | 
					  cursor: pointer;
 | 
				
			||||||
  border-radius: 3px;
 | 
					  border-radius: 3px;
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: calc(1em * 56/13); /* 56px for 13px font */
 | 
				
			||||||
  max-width: 750px;
 | 
					  max-width: 750px;
 | 
				
			||||||
  border: 1px solid ${css.colors.mediumGrey};
 | 
					  border: 1px solid ${css.colors.mediumGrey};
 | 
				
			||||||
  &:hover {
 | 
					  &:hover {
 | 
				
			||||||
    border-color: ${css.colors.slate};
 | 
					    border-color: ${css.colors.slate};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  .${cssList.className}-list & {
 | 
					 | 
				
			||||||
    min-height: calc(1em * 40/13); /* 40px for 13px font */
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  .${cssList.className}-card & {
 | 
					 | 
				
			||||||
    width: 300px;
 | 
					 | 
				
			||||||
    height: calc(1em * 56/13); /* 56px for 13px font */
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  @media ${css.mediaSmall} {
 | 
					 | 
				
			||||||
    .${cssList.className}-card & {
 | 
					 | 
				
			||||||
      width: calc(50% - 12px);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  @media ${css.mediaXSmall} {
 | 
					 | 
				
			||||||
    & {
 | 
					 | 
				
			||||||
      width: 100% !important;
 | 
					 | 
				
			||||||
      height: calc(1em * 56/13) !important; /* 56px for 13px font */
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
`);
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Holds icon in top left corner
 | 
					// Holds icon in top left corner
 | 
				
			||||||
@ -238,21 +196,17 @@ const cssMiddle = styled('div', `
 | 
				
			|||||||
  flex-wrap: wrap;
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
  margin-top: 6px;
 | 
					  margin-top: 6px;
 | 
				
			||||||
  margin-bottom: 4px;
 | 
					  margin-bottom: 4px;
 | 
				
			||||||
  .${cssList.className}-card & {
 | 
					 | 
				
			||||||
    margin: 0px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
`);
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const css60 = styled('div', `
 | 
					const cssTitleRow = styled('div', `
 | 
				
			||||||
  min-width: min(240px, 100%);
 | 
					  min-width: 100%;
 | 
				
			||||||
  flex: 6;
 | 
					 | 
				
			||||||
  margin-right: 4px;
 | 
					  margin-right: 4px;
 | 
				
			||||||
`);
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const css40 = styled('div', `
 | 
					const cssDetailsRow = styled('div', `
 | 
				
			||||||
  min-width: min(240px, 100%);
 | 
					  min-width: 100%;
 | 
				
			||||||
  flex: 4;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  gap: 8px;
 | 
				
			||||||
`);
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -275,29 +229,43 @@ const cssLine = styled('span', `
 | 
				
			|||||||
  overflow: hidden;
 | 
					  overflow: hidden;
 | 
				
			||||||
`);
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cssIdHoverWrapper = styled('div', `
 | 
					const cssTableIdWrapper = styled('div', `
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-grow: 1;
 | 
				
			||||||
 | 
					  min-width: 0;
 | 
				
			||||||
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cssTableRowsWrapper = styled('div', `
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-shrink: 0;
 | 
				
			||||||
 | 
					  width: 80px;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  align-items: baseline;
 | 
				
			||||||
 | 
					  color: ${css.colors.slate};
 | 
				
			||||||
 | 
					  line-height: 18px;
 | 
				
			||||||
 | 
					  padding: 0px 2px;
 | 
				
			||||||
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cssHoverWrapper = styled('div', `
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  overflow: hidden;
 | 
					  overflow: hidden;
 | 
				
			||||||
  cursor: default;
 | 
					  cursor: default;
 | 
				
			||||||
  align-items: baseline;
 | 
					  align-items: baseline;
 | 
				
			||||||
  color: ${css.colors.slate};
 | 
					  color: ${css.colors.slate};
 | 
				
			||||||
  transition: background 0.05s;
 | 
					  transition: background 0.05s;
 | 
				
			||||||
  padding: 1px 2px;
 | 
					  padding: 0px 2px;
 | 
				
			||||||
  line-height: 18px;
 | 
					  line-height: 18px;
 | 
				
			||||||
  &:hover {
 | 
					  &:hover {
 | 
				
			||||||
    background: ${css.colors.lightGrey};
 | 
					    background: ${css.colors.lightGrey};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  @media ${css.mediaSmall} {
 | 
					 | 
				
			||||||
    & {
 | 
					 | 
				
			||||||
      padding: 0px 2px !important;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
`);
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cssTableId = styled(cssLine, `
 | 
					const cssTableId = styled(cssLine, `
 | 
				
			||||||
  font-size: ${css.vars.smallFontSize};
 | 
					  font-size: ${css.vars.smallFontSize};
 | 
				
			||||||
`);
 | 
					`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cssTableRows = cssTableId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cssTableTitle = styled('div', `
 | 
					const cssTableTitle = styled('div', `
 | 
				
			||||||
  white-space: nowrap;
 | 
					  white-space: nowrap;
 | 
				
			||||||
`);
 | 
					`);
 | 
				
			||||||
 | 
				
			|||||||
@ -53,14 +53,14 @@ export class DocumentUsage extends Disposable {
 | 
				
			|||||||
  private readonly _rowMetrics: Computed<MetricOptions | null> =
 | 
					  private readonly _rowMetrics: Computed<MetricOptions | null> =
 | 
				
			||||||
    Computed.create(this, this._currentProduct, this._rowCount, (_use, product, rowCount) => {
 | 
					    Computed.create(this, this._currentProduct, this._rowCount, (_use, product, rowCount) => {
 | 
				
			||||||
      const features = product?.features;
 | 
					      const features = product?.features;
 | 
				
			||||||
      if (!features || typeof rowCount !== 'number') { return null; }
 | 
					      if (!features || typeof rowCount !== 'object') { return null; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const {baseMaxRowsPerDocument: maxRows} = features;
 | 
					      const {baseMaxRowsPerDocument: maxRows} = features;
 | 
				
			||||||
      // Invalid row limits are currently treated as if they are undefined.
 | 
					      // Invalid row limits are currently treated as if they are undefined.
 | 
				
			||||||
      const maxValue = maxRows && maxRows > 0 ? maxRows : undefined;
 | 
					      const maxValue = maxRows && maxRows > 0 ? maxRows : undefined;
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        name: 'Rows',
 | 
					        name: 'Rows',
 | 
				
			||||||
        currentValue: rowCount,
 | 
					        currentValue: rowCount.total,
 | 
				
			||||||
        maximumValue: maxValue ?? DEFAULT_MAX_ROWS,
 | 
					        maximumValue: maxValue ?? DEFAULT_MAX_ROWS,
 | 
				
			||||||
        unit: 'rows',
 | 
					        unit: 'rows',
 | 
				
			||||||
        shouldHideLimits: maxValue === undefined,
 | 
					        shouldHideLimits: maxValue === undefined,
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {DocAction, UserAction} from 'app/common/DocActions';
 | 
					import {DocAction, UserAction} from 'app/common/DocActions';
 | 
				
			||||||
 | 
					import {RowCounts} from 'app/common/DocUsage';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Metadata about the action.
 | 
					// Metadata about the action.
 | 
				
			||||||
export interface ActionInfo {
 | 
					export interface ActionInfo {
 | 
				
			||||||
@ -61,7 +62,7 @@ export interface SandboxActionBundle {
 | 
				
			|||||||
  calc: Array<EnvContent<DocAction>>;
 | 
					  calc: Array<EnvContent<DocAction>>;
 | 
				
			||||||
  undo: Array<EnvContent<DocAction>>;   // Inverse actions for all 'stored' actions.
 | 
					  undo: Array<EnvContent<DocAction>>;   // Inverse actions for all 'stored' actions.
 | 
				
			||||||
  retValues: any[];                     // Contains retValue for each of userActions.
 | 
					  retValues: any[];                     // Contains retValue for each of userActions.
 | 
				
			||||||
  rowCount: number;
 | 
					  rowCount: RowCounts;
 | 
				
			||||||
  // Mapping of keys (hashes of request args) to all unique requests made in a round of calculation
 | 
					  // Mapping of keys (hashes of request args) to all unique requests made in a round of calculation
 | 
				
			||||||
  requests?: Record<string, SandboxRequest>;
 | 
					  requests?: Record<string, SandboxRequest>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -53,7 +53,7 @@ export function getDataLimitRatio(
 | 
				
			|||||||
  const {rowCount, dataSizeBytes} = docUsage;
 | 
					  const {rowCount, dataSizeBytes} = docUsage;
 | 
				
			||||||
  const maxRows = productFeatures?.baseMaxRowsPerDocument;
 | 
					  const maxRows = productFeatures?.baseMaxRowsPerDocument;
 | 
				
			||||||
  const maxDataSize = productFeatures?.baseMaxDataSizePerDocument;
 | 
					  const maxDataSize = productFeatures?.baseMaxDataSizePerDocument;
 | 
				
			||||||
  const rowRatio = getUsageRatio(rowCount, maxRows);
 | 
					  const rowRatio = getUsageRatio(rowCount?.total, maxRows);
 | 
				
			||||||
  const dataSizeRatio = getUsageRatio(dataSizeBytes, maxDataSize);
 | 
					  const dataSizeRatio = getUsageRatio(dataSizeBytes, maxDataSize);
 | 
				
			||||||
  return Math.max(rowRatio, dataSizeRatio);
 | 
					  return Math.max(rowRatio, dataSizeRatio);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,14 @@
 | 
				
			|||||||
export interface DocumentUsage {
 | 
					export interface DocumentUsage {
 | 
				
			||||||
  rowCount?: number;
 | 
					  rowCount?: RowCounts;
 | 
				
			||||||
  dataSizeBytes?: number;
 | 
					  dataSizeBytes?: number;
 | 
				
			||||||
  attachmentsSizeBytes?: number;
 | 
					  attachmentsSizeBytes?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface RowCounts {
 | 
				
			||||||
 | 
					  total: number;
 | 
				
			||||||
 | 
					  [tableRef: number]: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type DataLimitStatus = 'approachingLimit' | 'gracePeriod' | 'deleteOnly' | null;
 | 
					export type DataLimitStatus = 'approachingLimit' | 'gracePeriod' | 'deleteOnly' | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type DocUsageOrPending = {
 | 
					type DocUsageOrPending = {
 | 
				
			||||||
 | 
				
			|||||||
@ -51,6 +51,7 @@ import {
 | 
				
			|||||||
  DocUsageSummary,
 | 
					  DocUsageSummary,
 | 
				
			||||||
  FilteredDocUsageSummary,
 | 
					  FilteredDocUsageSummary,
 | 
				
			||||||
  getUsageRatio,
 | 
					  getUsageRatio,
 | 
				
			||||||
 | 
					  RowCounts,
 | 
				
			||||||
} from 'app/common/DocUsage';
 | 
					} from 'app/common/DocUsage';
 | 
				
			||||||
import {normalizeEmail} from 'app/common/emails';
 | 
					import {normalizeEmail} from 'app/common/emails';
 | 
				
			||||||
import {ErrorWithCode} from 'app/common/ErrorWithCode';
 | 
					import {ErrorWithCode} from 'app/common/ErrorWithCode';
 | 
				
			||||||
@ -320,7 +321,7 @@ export class ActiveDoc extends EventEmitter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  public get rowLimitRatio(): number {
 | 
					  public get rowLimitRatio(): number {
 | 
				
			||||||
    return getUsageRatio(
 | 
					    return getUsageRatio(
 | 
				
			||||||
      this._docUsage?.rowCount,
 | 
					      this._docUsage?.rowCount?.total,
 | 
				
			||||||
      this._product?.features.baseMaxRowsPerDocument
 | 
					      this._product?.features.baseMaxRowsPerDocument
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -1666,10 +1667,10 @@ export class ActiveDoc extends EventEmitter {
 | 
				
			|||||||
    return this._granularAccess;
 | 
					    return this._granularAccess;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async updateRowCount(rowCount: number, docSession: OptDocSession | null) {
 | 
					  public async updateRowCount(rowCount: RowCounts, docSession: OptDocSession | null) {
 | 
				
			||||||
    // Up-to-date row counts are included in every DocUserAction, so we can skip broadcasting here.
 | 
					    // Up-to-date row counts are included in every DocUserAction, so we can skip broadcasting here.
 | 
				
			||||||
    await this._updateDocUsage({rowCount}, {broadcastUsageToClients: false});
 | 
					    await this._updateDocUsage({rowCount}, {broadcastUsageToClients: false});
 | 
				
			||||||
    log.rawInfo('Sandbox row count', {...this.getLogMeta(docSession), rowCount});
 | 
					    log.rawInfo('Sandbox row count', {...this.getLogMeta(docSession), rowCount: rowCount.total});
 | 
				
			||||||
    await this._checkDataLimitRatio();
 | 
					    await this._checkDataLimitRatio();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Calculating data size is potentially expensive, so skip calculating it unless the
 | 
					    // Calculating data size is potentially expensive, so skip calculating it unless the
 | 
				
			||||||
@ -2269,7 +2270,7 @@ function createEmptySandboxActionBundle(): SandboxActionBundle {
 | 
				
			|||||||
    calc: [],
 | 
					    calc: [],
 | 
				
			||||||
    undo: [],
 | 
					    undo: [],
 | 
				
			||||||
    retValues: [],
 | 
					    retValues: [],
 | 
				
			||||||
    rowCount: 0,
 | 
					    rowCount: {total: 0},
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1262,11 +1262,13 @@ class Engine(object):
 | 
				
			|||||||
    self._unused_lookups.add(lookup_map_column)
 | 
					    self._unused_lookups.add(lookup_map_column)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def count_rows(self):
 | 
					  def count_rows(self):
 | 
				
			||||||
    return sum(
 | 
					    result = {"total": 0}
 | 
				
			||||||
      table._num_rows()
 | 
					    for table_rec in self.docmodel.tables.all:
 | 
				
			||||||
      for table_id, table in six.iteritems(self.tables)
 | 
					      if useractions.is_user_table(table_rec.tableId):
 | 
				
			||||||
      if useractions.is_user_table(table_id) and not table._summary_source_table
 | 
					        count = self.tables[table_rec.tableId]._num_rows()
 | 
				
			||||||
    )
 | 
					        result[table_rec.id] = count
 | 
				
			||||||
 | 
					        result["total"] += count
 | 
				
			||||||
 | 
					    return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def apply_user_actions(self, user_actions, user=None):
 | 
					  def apply_user_actions(self, user_actions, user=None):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
				
			|||||||
@ -1266,7 +1266,7 @@ class TestUserActions(test_engine.EngineTestCase):
 | 
				
			|||||||
    for i in range(20):
 | 
					    for i in range(20):
 | 
				
			||||||
      self.add_record("Address", None)
 | 
					      self.add_record("Address", None)
 | 
				
			||||||
      self.assertEqual(i + 1, table._num_rows())
 | 
					      self.assertEqual(i + 1, table._num_rows())
 | 
				
			||||||
      self.assertEqual(i + 1, self.engine.count_rows())
 | 
					      self.assertEqual({1: i + 1, "total": i + 1}, self.engine.count_rows())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def test_raw_view_section_restrictions(self):
 | 
					  def test_raw_view_section_restrictions(self):
 | 
				
			||||||
    # load_sample handles loading basic metadata, but doesn't create any view sections
 | 
					    # load_sample handles loading basic metadata, but doesn't create any view sections
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user