diff --git a/app/client/components/modals.ts b/app/client/components/modals.ts index 06ed405f..ad319755 100644 --- a/app/client/components/modals.ts +++ b/app/client/components/modals.ts @@ -9,8 +9,11 @@ import {icon} from 'app/client/ui2018/icons'; import {cssModalTooltip, modalTooltip} from 'app/client/ui2018/modals'; import {dom, DomContents, keyframes, observable, styled, svg} from 'grainjs'; import {IPopupOptions} from 'popweasel'; +import {makeT} from 'app/client/lib/localization'; import merge = require('lodash/merge'); +const t = makeT('modals'); + /** * This is a file for all custom and pre-configured popups, modals, toasts and tooltips, used * in more then one component. @@ -35,19 +38,21 @@ export function buildConfirmDelete( Escape: () => ctl.close(), Enter: () => { onSave(remember.get()); ctl.close(); }, }), - dom('div', `Are you sure you want to delete ${single ? 'this' : 'these'} record${single ? '' : 's'}?`, + dom('div', single ? + t(`Are you sure you want to delete this record?`) + : t(`Are you sure you want to delete these records?`), dom.style('margin-bottom', '10px'), ), dom('div', - labeledSquareCheckbox(remember, "Don't ask again.", testId('confirm-remember')), + labeledSquareCheckbox(remember, t("Don't ask again."), testId('confirm-remember')), dom.style('margin-bottom', '10px'), ), cssButtons( - primaryButton('Delete', testId('confirm-save'), dom.on('click', () => { + primaryButton(t('Delete'), testId('confirm-save'), dom.on('click', () => { onSave(remember.get()); ctl.close(); })), - basicButton('Cancel', testId('confirm-cancel'), dom.on('click', () => ctl.close())) + basicButton(t('Cancel'), testId('confirm-cancel'), dom.on('click', () => ctl.close())) ) ), {} ); @@ -81,9 +86,9 @@ export function showDeprecatedWarning( dom.style('justify-content', 'space-between'), dom.style('align-items', 'center'), dom('div', - labeledSquareCheckbox(remember, "Don't show again.", testId('confirm-remember')), + labeledSquareCheckbox(remember, t("Don't show again."), testId('confirm-remember')), ), - basicButton('Dismiss', testId('confirm-save'), + basicButton(t('Dismiss'), testId('confirm-save'), dom.on('click', () => { ctl.close(); onClose(remember.get()); }) ) ), @@ -105,7 +110,7 @@ export function showDeprecatedWarning( export function reportUndo( doc: GristDoc, messageLabel: string, - buttonLabel = 'Undo to restore' + buttonLabel = t('Undo to restore') ) { // First create a notification with a button to undo the delete. let notification = reportSuccess(messageLabel, { @@ -179,12 +184,12 @@ export function showBehavioralPrompt( dom.style('align-items', 'center'), dom('div', cssSkipTipsCheckbox(dontShowTips, - cssSkipTipsCheckboxLabel("Don't show tips"), + cssSkipTipsCheckboxLabel(t("Don't show tips")), testId('behavioral-prompt-dont-show-tips') ), dom.style('visibility', hideDontShowTips ? 'hidden' : ''), ), - cssDismissPromptButton('Got it', testId('behavioral-prompt-dismiss'), + cssDismissPromptButton(t('Got it'), testId('behavioral-prompt-dismiss'), dom.on('click', () => { onClose(dontShowTips.get()); ctl.close(); }) ), ), diff --git a/app/server/lib/Client.ts b/app/server/lib/Client.ts index e78ef3d9..359f3020 100644 --- a/app/server/lib/Client.ts +++ b/app/server/lib/Client.ts @@ -526,11 +526,19 @@ export class Client { } } + private async _onMessage(message: string): Promise { + try { + await this._onMessageImpl(message); + } catch (err) { + this._log.warn(null, 'onMessage error received for message "%s": %s', shortDesc(message), err.stack); + } + } + /** * Processes a request from a client. All requests from a client get a response, at least to * indicate success or failure. */ - private async _onMessage(message: string): Promise { + private async _onMessageImpl(message: string): Promise { const request = JSON.parse(message); if (request.beat) { // this is a heart beat, to keep the websocket alive. No need to reply. diff --git a/static/locales/en.client.json b/static/locales/en.client.json index 5660667d..679dbd10 100644 --- a/static/locales/en.client.json +++ b/static/locales/en.client.json @@ -433,7 +433,19 @@ "Created by": "Created by", "Detect duplicates in...": "Detect duplicates in...", "Last updated at": "Last updated at", - "Last updated by": "Last updated by" + "Last updated by": "Last updated by", + "Any": "Any", + "Numeric": "Numeric", + "Text": "Text", + "Integer": "Integer", + "Toggle": "Toggle", + "Date": "Date", + "DateTime": "DateTime", + "Choice": "Choice", + "Choice List": "Choice List", + "Reference": "Reference", + "Reference List": "Reference List", + "Attachment": "Attachment" }, "GristDoc": { "Added new linked section to view {{viewName}}": "Added new linked section to view {{viewName}}", @@ -622,7 +634,23 @@ "Widget": "Widget", "You do not have edit access to this document": "You do not have edit access to this document", "Add referenced columns": "Add referenced columns", - "Reset form": "Reset form" + "Reset form": "Reset form", + "Configuration": "Configuration", + "Default field value": "Default field value", + "Display button": "Display button", + "Enter text": "Enter text", + "Field rules": "Field rules", + "Field title": "Field title", + "Hidden field": "Hidden field", + "Layout": "Layout", + "Redirect automatically after submission": "Redirect automatically after submission", + "Redirection": "Redirection", + "Required field": "Required field", + "Submission": "Submission", + "Submit another response": "Submit another response", + "Submit button label": "Submit button label", + "Success text": "Success text", + "Table column name": "Table column name" }, "RowContextMenu": { "Copy anchor link": "Copy anchor link", @@ -761,7 +789,8 @@ "Show raw data": "Show raw data", "Widget options": "Widget options", "Add to page": "Add to page", - "Collapse widget": "Collapse widget" + "Collapse widget": "Collapse widget", + "Create a form": "Create a form" }, "ViewSectionMenu": { "(customized)": "(customized)", @@ -863,7 +892,16 @@ "modals": { "Cancel": "Cancel", "Ok": "OK", - "Save": "Save" + "Save": "Save", + "Are you sure you want to delete these records?": "Are you sure you want to delete these records?", + "Are you sure you want to delete this record?": "Are you sure you want to delete this record?", + "Delete": "Delete", + "Dismiss": "Dismiss", + "Don't ask again.": "Don't ask again.", + "Don't show again.": "Don't show again.", + "Don't show tips": "Don't show tips", + "Undo to restore": "Undo to restore", + "Got it": "Got it" }, "pages": { "Duplicate Page": "Duplicate Page", @@ -1266,5 +1304,28 @@ "Publish your form?": "Publish your form?", "Unpublish": "Unpublish", "Unpublish your form?": "Unpublish your form?" + }, + "Editor": { + "Delete": "Delete" + }, + "Menu": { + "Building blocks": "Building blocks", + "Columns": "Columns", + "Copy": "Copy", + "Cut": "Cut", + "Insert question above": "Insert question above", + "Insert question below": "Insert question below", + "Paragraph": "Paragraph", + "Paste": "Paste", + "Separator": "Separator", + "Unmapped fields": "Unmapped fields" + }, + "UnmappedFieldsConfig": { + "Clear": "Clear", + "Map fields": "Map fields", + "Mapped": "Mapped", + "Select All": "Select All", + "Unmap fields": "Unmap fields", + "Unmapped": "Unmapped" } } diff --git a/test/server/Comm.ts b/test/server/Comm.ts index 3d95dffe..15ecc817 100644 --- a/test/server/Comm.ts +++ b/test/server/Comm.ts @@ -188,6 +188,24 @@ describe('Comm', function() { ]); }); + it('should only log warning for malformed JSON data', async function () { + const logMessages = await testUtils.captureLog('warn', async () => { + ws.send('foobar'); + }, {waitForFirstLog: true}); + testUtils.assertMatchArray(logMessages, [ + /^warn: Client.* Unexpected token.*/ + ]); + }); + + it('should log warning when null value is passed', async function () { + const logMessages = await testUtils.captureLog('warn', async () => { + ws.send('null'); + }, {waitForFirstLog: true}); + testUtils.assertMatchArray(logMessages, [ + /^warn: Client.*Cannot read properties of null*/ + ]); + }); + it("should support app-level events correctly", async function() { comm!.broadcastMessage('fooType' as any, 'hello'); comm!.broadcastMessage('barType' as any, 'world'); diff --git a/test/server/testUtils.ts b/test/server/testUtils.ts index 3f0a5cb6..a394a005 100644 --- a/test/server/testUtils.ts +++ b/test/server/testUtils.ts @@ -126,25 +126,32 @@ export function setTmpLogLevel(level: string, optCaptureFunc?: (level: string, m */ export async function captureLog( minLevel: string, callback: (messages: string[]) => void|Promise, - options: {timestamp: boolean} = {timestamp: false} + options: {timestamp?: boolean, waitForFirstLog?: boolean} = {timestamp: false, waitForFirstLog: false} ): Promise { const messages: string[] = []; const prevLogLevel = log.transports.file.level; const name = _.uniqueId('CaptureLog'); - function capture(level: string, msg: string, meta: any) { - if ((log as any).levels[level] <= (log as any).levels[minLevel]) { // winston types are off? - const timePrefix = options.timestamp ? new Date().toISOString() + ' ' : ''; - messages.push(`${timePrefix}${level}: ${msg}${meta ? ' ' + serialize(meta) : ''}`); + const captureFirstLogPromise = new Promise((resolve) => { + function capture(level: string, msg: string, meta: any) { + if ((log as any).levels[level] <= (log as any).levels[minLevel]) { // winston types are off? + const timePrefix = options.timestamp ? new Date().toISOString() + ' ' : ''; + messages.push(`${timePrefix}${level}: ${msg}${meta ? ' ' + serialize(meta) : ''}`); + resolve(null); + } } - } - if (!process.env.VERBOSE) { - log.transports.file.level = -1 as any; // Suppress all log output. - } - log.add(CaptureTransport as any, { captureFunc: capture, name, level: minLevel}); // types are off. + if (!process.env.VERBOSE) { + log.transports.file.level = -1 as any; // Suppress all log output. + } + log.add(CaptureTransport as any, { captureFunc: capture, name, level: minLevel}); // types are off. + }); + try { await callback(messages); + if (options.waitForFirstLog) { + await captureFirstLogPromise; + } } finally { log.remove(name); log.transports.file.level = prevLogLevel;