From e26dd6025f1775e17ded6e10e0264c2034e76719 Mon Sep 17 00:00:00 2001 From: ttsukagoshi Date: Wed, 21 Feb 2024 01:27:39 +0900 Subject: [PATCH 1/2] Add `checkUsage()` and related tests resolve #73 --- src/sheetsl.ts | 59 +++++++++++++++++++++++++++++++++---- tests/checkUsage.test.ts | 51 ++++++++++++++++++++++++++++++++ tests/deepLGetUsage.test.ts | 32 ++++++++++++++++++++ 3 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 tests/checkUsage.test.ts create mode 100644 tests/deepLGetUsage.test.ts diff --git a/src/sheetsl.ts b/src/sheetsl.ts index 82f5fb8..2632c22 100644 --- a/src/sheetsl.ts +++ b/src/sheetsl.ts @@ -67,6 +67,15 @@ interface DeepLTranslationObj { text: string; } +interface DeepLUsageResponse { + character_count: number; + character_limit: number; + document_limit?: number; + document_count?: number; + team_document_limit?: number; + team_document_count?: number; +} + /** * The type of language that should be returned in the GET request * to the DeepL API to retrieve its supported languages. @@ -86,16 +95,17 @@ type PropertiesObj = Record; function onOpen(): void { const ui = SpreadsheetApp.getUi(); ui.createAddonMenu() + .addItem('Translate', 'translateSelectedRange') + .addSeparator() + .addItem('Check usage', 'checkUsage') .addSubMenu( ui .createMenu('Settings') - .addItem('Set DeepL API Key', 'setDeeplApiKey') - .addItem('Delete DeepL API Key', 'deleteDeeplApiKey') + .addItem('Set language', 'setLanguage') .addSeparator() - .addItem('Set Language', 'setLanguage'), + .addItem('Set DeepL API Key', 'setDeeplApiKey') + .addItem('Delete DeepL API Key', 'deleteDeeplApiKey'), ) - .addSeparator() - .addItem('Translate', 'translateSelectedRange') .addToUi(); } @@ -397,6 +407,23 @@ export function splitLongArray( return returnArray; } +/** + * Check the usage of the DeepL API + * and show the result to the user in an alert box. + */ +export function checkUsage(): void { + const ui = SpreadsheetApp.getUi(); + try { + const usage = deepLGetUsage(); + ui.alert( + `[${ADDON_NAME}] DeepL API Usage Status:\n\nYou are currently using ${usage.character_count} characters out of ${usage.character_limit} characters (${Math.round((10 * 100 * usage.character_count) / usage.character_limit) / 10}%).`, + ); + } catch (error) { + console.error((error as Error).stack); + ui.alert((error as Error).message); + } +} + /** * Call the DeepL API on the `translate` endpoint * @param sourceText Array of texts to translate @@ -480,6 +507,28 @@ export function deepLGetLanguages( return JSON.parse(response.getContentText()) as DeepLSupportedLanguages[]; } +/** + * Retrieve the usage of the DeepL API. + * @returns The usage of the DeepL API. + * @see https://www.deepl.com/docs-api/general/get-usage + */ +export function deepLGetUsage(): DeepLUsageResponse { + const endpoint = 'usage'; + // API key + const apiKey = getDeepLApiKey(); + const baseUrl = getDeepLApiBaseUrl(apiKey); + // Call the DeepL API + const options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = { + method: 'get', + headers: { Authorization: `DeepL-Auth-Key ${apiKey}` }, + muteHttpExceptions: true, + }; + const response = handleDeepLErrors( + UrlFetchApp.fetch(baseUrl + endpoint, options), + ); + return JSON.parse(response.getContentText()) as DeepLUsageResponse; +} + /** * Get the length of a given string in bytes. * @param text The string of which to get the bytes. diff --git a/tests/checkUsage.test.ts b/tests/checkUsage.test.ts new file mode 100644 index 0000000..d42f7b4 --- /dev/null +++ b/tests/checkUsage.test.ts @@ -0,0 +1,51 @@ +import { checkUsage } from '../src/sheetsl'; + +describe('checkUsage', () => { + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-empty-function + jest.spyOn(console, 'error').mockImplementation(() => {}); + global.PropertiesService = { + getUserProperties: jest.fn(() => ({ + getProperty: jest.fn(() => 'SampleApiKey'), + })), + } as unknown as GoogleAppsScript.Properties.PropertiesService; + global.SpreadsheetApp = { + getUi: jest.fn(() => ({ + alert: jest.fn(), + })), + } as unknown as GoogleAppsScript.Spreadsheet.SpreadsheetApp; + }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should check usage without errors', () => { + global.UrlFetchApp = { + fetch: jest.fn(() => ({ + getContentText: jest.fn(() => + JSON.stringify({ + character_count: 10, + character_limit: 50, + }), + ), + getResponseCode: jest.fn(() => 200), // Mock a successful response + })), + } as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp; + checkUsage(); + expect(console.error).not.toHaveBeenCalled(); + }); + it('should catch an error', () => { + global.UrlFetchApp = { + fetch: jest.fn(() => ({ + getContentText: jest.fn(() => + JSON.stringify({ + character_count: 10, + character_limit: 50, + }), + ), + getResponseCode: jest.fn(() => 500), // Mock an unsuccessful response + })), + } as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp; + checkUsage(); + expect(console.error).toHaveBeenCalled(); + }); +}); diff --git a/tests/deepLGetUsage.test.ts b/tests/deepLGetUsage.test.ts new file mode 100644 index 0000000..e2c6603 --- /dev/null +++ b/tests/deepLGetUsage.test.ts @@ -0,0 +1,32 @@ +import { deepLGetUsage } from '../src/sheetsl'; + +describe('deepLGetUsage', () => { + beforeEach(() => { + global.PropertiesService = { + getUserProperties: jest.fn(() => ({ + getProperty: jest.fn(() => 'SampleApiKey'), + })), + } as unknown as GoogleAppsScript.Properties.PropertiesService; + }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should get usage', () => { + global.UrlFetchApp = { + fetch: jest.fn(() => ({ + getContentText: jest.fn(() => + JSON.stringify({ + character_count: 10, + character_limit: 50, + }), + ), + getResponseCode: jest.fn(() => 200), // Mock a successful response + })), + } as unknown as GoogleAppsScript.URL_Fetch.UrlFetchApp; + const usage = deepLGetUsage(); + expect(usage).toEqual({ + character_count: 10, + character_limit: 50, + }); + }); +}); From d979f99267759c8a6a62b9155e42ac1f74f6a4b4 Mon Sep 17 00:00:00 2001 From: ttsukagoshi Date: Wed, 21 Feb 2024 01:30:45 +0900 Subject: [PATCH 2/2] Refactor `deepLGetLanguages()` --- src/sheetsl.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sheetsl.ts b/src/sheetsl.ts index 2632c22..ddb00bc 100644 --- a/src/sheetsl.ts +++ b/src/sheetsl.ts @@ -499,10 +499,13 @@ export function deepLGetLanguages( const apiKey = getDeepLApiKey(); const baseUrl = getDeepLApiBaseUrl(apiKey); // Call the DeepL API - const url = baseUrl + endpoint + `?auth_key=${apiKey}&type=${type}`; - const response = handleDeepLErrors( - UrlFetchApp.fetch(url, { muteHttpExceptions: true }), - ); + const url = baseUrl + endpoint + `?type=${type}`; + const options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = { + method: 'get', + headers: { Authorization: `DeepL-Auth-Key ${apiKey}` }, + muteHttpExceptions: true, + }; + const response = handleDeepLErrors(UrlFetchApp.fetch(url, options)); return JSON.parse(response.getContentText()) as DeepLSupportedLanguages[]; }