Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add menu to get DeepL API usage status #154

Merged
merged 2 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 61 additions & 9 deletions src/sheetsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -86,16 +95,17 @@ type PropertiesObj = Record<string, string>;
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();
}

Expand Down Expand Up @@ -397,6 +407,23 @@ export function splitLongArray<T>(
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
Expand Down Expand Up @@ -472,14 +499,39 @@ 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[];
}

/**
* 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.
Expand Down
51 changes: 51 additions & 0 deletions tests/checkUsage.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
32 changes: 32 additions & 0 deletions tests/deepLGetUsage.test.ts
Original file line number Diff line number Diff line change
@@ -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,
});
});
});