diff --git a/src/__mocks__/setting.ts b/src/__mocks__/setting.ts index 6c44286..013e4ef 100644 --- a/src/__mocks__/setting.ts +++ b/src/__mocks__/setting.ts @@ -4,6 +4,12 @@ export class Setting { } static load(): object { - return { token: 'foo' } + return { + token: 'foo', + ondemandApiEnabled: false, + ondemandApiCallMode: 'default', + isOndemandApiCallModeDefault: (): boolean => true, + isOndemandApiCallModeForce: (): boolean => false + } } } diff --git a/src/custom-functions/v3/bcode-daily.test.ts b/src/custom-functions/v3/bcode-daily.test.ts index a0977a7..ba4a6b3 100644 --- a/src/custom-functions/v3/bcode-daily.test.ts +++ b/src/custom-functions/v3/bcode-daily.test.ts @@ -16,7 +16,7 @@ test('bcodeDaily', () => { expect(DailyCache.get(ticker, date)).toBeNull() const client = new CachingBuffettCodeApiClientV3('token') - const result = bcodeDaily(client, ticker, date, propertyName, false) + const result = bcodeDaily(client, ticker, date, propertyName, false, false) expect(result).toEqual(new BcodeResult(propertyName, 550294097166, '円')) expect(DailyCache.get(ticker, date)).not.toBeNull() diff --git a/src/custom-functions/v3/bcode-daily.ts b/src/custom-functions/v3/bcode-daily.ts index 8859374..8f49559 100644 --- a/src/custom-functions/v3/bcode-daily.ts +++ b/src/custom-functions/v3/bcode-daily.ts @@ -15,7 +15,8 @@ export function bcodeDaily( ticker: string, date: DateParam, propertyName: string, - ondemandApiEnabled: boolean + ondemandApiEnabled: boolean, + forceOndemandApiEnabled: boolean ): BcodeResult { const companyService = new CompanyService(ticker, client) if (!companyService.isSupportedTicker()) { @@ -23,7 +24,7 @@ export function bcodeDaily( } let daily: Daily - if (companyService.isOndemandDailyApiPeriod(date)) { + if (forceOndemandApiEnabled || companyService.isOndemandDailyApiPeriod(date)) { if (!ondemandApiEnabled) { throw new OndemandApiNotEnabledError() } diff --git a/src/custom-functions/v3/bcode-quarter.test.ts b/src/custom-functions/v3/bcode-quarter.test.ts index 56f8b6a..c35b598 100644 --- a/src/custom-functions/v3/bcode-quarter.test.ts +++ b/src/custom-functions/v3/bcode-quarter.test.ts @@ -17,7 +17,7 @@ test('bcodeQuarter', () => { expect(QuarterCache.get(ticker, period)).toBeNull() const client = new CachingBuffettCodeApiClientV3('token') - const result = bcodeQuarter(client, ticker, YearQuarterParam.fromYearQuarter(period), propertyName, false) + const result = bcodeQuarter(client, ticker, YearQuarterParam.fromYearQuarter(period), propertyName, false, false) expect(result).toEqual(new BcodeResult(propertyName, 12513000000, '円')) expect(QuarterCache.get(ticker, period)).not.toBeNull() diff --git a/src/custom-functions/v3/bcode-quarter.ts b/src/custom-functions/v3/bcode-quarter.ts index 1f92dec..3e6faf1 100644 --- a/src/custom-functions/v3/bcode-quarter.ts +++ b/src/custom-functions/v3/bcode-quarter.ts @@ -15,7 +15,8 @@ export function bcodeQuarter( ticker: string, period: YearQuarterParam, propertyName: string, - ondemandApiEnabled: boolean + ondemandApiEnabled: boolean, + forceOndemandApiEnabled: boolean ): BcodeResult { const companyService = new CompanyService(ticker, client) if (!companyService.isSupportedTicker()) { @@ -23,7 +24,7 @@ export function bcodeQuarter( } let quarter: Quarter - if (companyService.isOndemandQuarterApiPeriod(period)) { + if (forceOndemandApiEnabled || companyService.isOndemandQuarterApiPeriod(period)) { if (!ondemandApiEnabled) { throw new OndemandApiNotEnabledError() } diff --git a/src/custom-functions/v3/bcode.ts b/src/custom-functions/v3/bcode.ts index b9acc96..759f1cb 100644 --- a/src/custom-functions/v3/bcode.ts +++ b/src/custom-functions/v3/bcode.ts @@ -78,9 +78,23 @@ export function bcode( const parsedPeriod = PeriodParser.parse(period) let result: BcodeResult if (parsedPeriod instanceof DateParam) { - result = bcodeDaily(client, ticker, parsedPeriod, propertyName, setting.ondemandApiEnabled) + result = bcodeDaily( + client, + ticker, + parsedPeriod, + propertyName, + setting.ondemandApiEnabled, + setting.isOndemandApiCallModeForce() + ) } else { - result = bcodeQuarter(client, ticker, parsedPeriod, propertyName, setting.ondemandApiEnabled) + result = bcodeQuarter( + client, + ticker, + parsedPeriod, + propertyName, + setting.ondemandApiEnabled, + setting.isOndemandApiCallModeForce() + ) } return result.format(isRawValue, isWithUnits) diff --git a/src/main.ts b/src/main.ts index 0db9b6c..b4643bf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,6 +17,7 @@ global.onInstall = (): void => { const setting = Setting.load() setting.setDefaultToken() setting.setDefaultOndemandApiEnabled() + setting.setDefaultOndemandApiCallMode() setting.save() global.onOpen() diff --git a/src/services/csv-exporter.ts b/src/services/csv-exporter.ts index 3f7d44c..b91dcbc 100644 --- a/src/services/csv-exporter.ts +++ b/src/services/csv-exporter.ts @@ -35,8 +35,15 @@ export class CsvExporter { const ondemandQuarterApiPeriodRange = new OndemandApiPeriodRange(companyService) - const ondemandQuarterApiPeriods = ondemandQuarterApiPeriodRange.selectOndemandQuarterApiPeriod(ticker, range) - const quarterApiPeriods = ondemandQuarterApiPeriodRange.filterOndemandQuarterApiPeriod(ticker, range) + let ondemandQuarterApiPeriods = [] + let quarterApiPeriods = [] + + if (setting.isOndemandApiCallModeForce()) { + ondemandQuarterApiPeriods = range.range() + } else { + ondemandQuarterApiPeriods = ondemandQuarterApiPeriodRange.selectOndemandQuarterApiPeriod(ticker, range) + quarterApiPeriods = ondemandQuarterApiPeriodRange.filterOndemandQuarterApiPeriod(ticker, range) + } if (ondemandQuarterApiPeriods.length > 0 && !setting.ondemandApiEnabled) { throw new Error( diff --git a/src/setting.test.ts b/src/setting.test.ts index f80c815..413d1db 100644 --- a/src/setting.test.ts +++ b/src/setting.test.ts @@ -5,7 +5,10 @@ declare const global: any test('load', () => { const mockGetProperty = jest.fn() - mockGetProperty.mockReturnValueOnce('foo').mockReturnValueOnce('true') + mockGetProperty + .mockReturnValueOnce('foo') + .mockReturnValueOnce('true') + .mockReturnValueOnce('force') global.PropertiesService = { getUserProperties: (): object => { @@ -16,12 +19,13 @@ test('load', () => { } const setting = Setting.load() - expect(mockGetProperty.mock.calls.length).toBe(2) + expect(mockGetProperty.mock.calls.length).toBe(3) expect(setting.token).toBe('foo') expect(setting.ondemandApiEnabled).toBe(true) + expect(setting.ondemandApiCallMode).toBe('force') }) -test('load__ondemandApiEnabledCasting', () => { +test('load (ondemandApiEnabled casting)', () => { const mockGetProperty = jest.fn() mockGetProperty.mockReturnValue(undefined) @@ -37,6 +41,76 @@ test('load__ondemandApiEnabledCasting', () => { expect(setting.ondemandApiEnabled).toBe(false) }) +test('load (ondemandApiCallMode fallback)', () => { + const mockGetProperty = jest.fn() + mockGetProperty.mockReturnValue(undefined) + + global.PropertiesService = { + getUserProperties: (): object => { + return { + getProperty: mockGetProperty + } + } + } + + const setting = Setting.load() + expect(setting.ondemandApiCallMode).toBe(Setting.defaultOndemandApiCallMode) +}) + +test('isValid (true)', () => { + const mockGetProperty = jest.fn() + + global.PropertiesService = { + getUserProperties: (): object => { + return { + getProperty: mockGetProperty + } + } + } + + const setting = Setting.load() + setting.token = 'bar' + setting.ondemandApiEnabled = true + setting.ondemandApiCallMode = 'force' + expect(setting.isValid()).toBeTruthy() +}) + +test('isValid (true)', () => { + const mockGetProperty = jest.fn() + + global.PropertiesService = { + getUserProperties: (): object => { + return { + getProperty: mockGetProperty + } + } + } + + const setting = Setting.load() + setting.token = 'bar' + setting.ondemandApiEnabled = true + setting.ondemandApiCallMode = 'foo' as 'default' | 'force' + expect(setting.isValid()).toBeFalsy() +}) + +test('isValid (ondemandApiEnabled is false but ondemandApiCallMode is changed)', () => { + const mockGetProperty = jest.fn() + + global.PropertiesService = { + getUserProperties: (): object => { + return { + getProperty: mockGetProperty + } + } + } + + const setting = Setting.load() + setting.token = 'bar' + setting.ondemandApiEnabled = false + setting.ondemandApiCallMode = 'force' + expect(setting.isValid()).toBeFalsy() +}) + test('save', () => { const mockGetProperty = jest.fn() const mockSetProperty = jest.fn() @@ -53,11 +127,33 @@ test('save', () => { const setting = Setting.load() setting.token = 'bar' setting.ondemandApiEnabled = true + setting.ondemandApiCallMode = 'default' setting.save() - expect(mockSetProperty.mock.calls.length).toBe(2) + expect(mockSetProperty.mock.calls.length).toBe(3) expect(mockSetProperty.mock.calls[0]).toEqual([Setting.tokenProperty, 'bar']) - expect(mockSetProperty.mock.calls[1]).toEqual([Setting.ondemandApiEnabledProperty, true]) + expect(mockSetProperty.mock.calls[1]).toEqual([Setting.ondemandApiEnabledProperty, 'true']) + expect(mockSetProperty.mock.calls[2]).toEqual([Setting.ondemandApiCallModeProperty, 'default']) +}) + +test('save (ondemandApiEnabled is false but ondemandApiCallMode is changed)', () => { + const mockGetProperty = jest.fn() + const mockSetProperty = jest.fn() + + global.PropertiesService = { + getUserProperties: (): object => { + return { + getProperty: mockGetProperty, + setProperty: mockSetProperty + } + } + } + + const setting = Setting.load() + setting.token = 'bar' + setting.ondemandApiEnabled = false + setting.ondemandApiCallMode = 'force' + expect(() => setting.save()).toThrow(Error) }) test('setDefaultToken', () => { @@ -79,6 +175,44 @@ test('setDefaultToken', () => { expect(setting.token).toBe(Setting.defaultToken) }) +test('setDefaultOndemandApiEnabled', () => { + const mockGetProperty = jest.fn() + mockGetProperty.mockReturnValue(undefined) + + global.PropertiesService = { + getUserProperties: (): object => { + return { + getProperty: mockGetProperty + } + } + } + + const setting = Setting.load() + expect(setting.ondemandApiEnabled).toBeFalsy() + + setting.setDefaultToken() + expect(setting.ondemandApiEnabled).toBe(Setting.defaultOndemandApiEnabled) +}) + +test('setDefaultOndemandApiCallMode', () => { + const mockGetProperty = jest.fn() + mockGetProperty.mockReturnValue(undefined) + + global.PropertiesService = { + getUserProperties: (): object => { + return { + getProperty: mockGetProperty + } + } + } + + const setting = Setting.load() + expect(setting.ondemandApiCallMode).toBe(Setting.defaultOndemandApiCallMode) + + setting.setDefaultToken() + expect(setting.ondemandApiCallMode).toBe(Setting.defaultOndemandApiCallMode) +}) + test('toObject', () => { const mockGetProperty = jest.fn() mockGetProperty.mockReturnValue(undefined) @@ -94,9 +228,22 @@ test('toObject', () => { const setting = Setting.load() setting.setDefaultToken() setting.setDefaultOndemandApiEnabled() + setting.setDefaultOndemandApiCallMode() expect(setting.toObject()).toStrictEqual({ token: Setting.defaultToken, - ondemandApiEnabled: Setting.defaultOndemandApiEnabled + ondemandApiEnabled: Setting.defaultOndemandApiEnabled, + ondemandApiCallMode: Setting.defaultOndemandApiCallMode }) }) + +test('Setting.validOndemanApiCallModes', () => { + expect(Setting.validOndemanApiCallModes()).toEqual(['default', 'force']) +}) + +test('Setting.castOndemandApiCallModeString', () => { + expect(Setting.castOndemandApiCallModeString('default')).toEqual('default') + expect(Setting.castOndemandApiCallModeString('force')).toEqual('force') + expect(() => Setting.castOndemandApiCallModeString('FORCE')).toThrow(TypeError) + expect(() => Setting.castOndemandApiCallModeString('foo')).toThrow(TypeError) +}) diff --git a/src/setting.ts b/src/setting.ts index 4f438ac..91d45fb 100644 --- a/src/setting.ts +++ b/src/setting.ts @@ -1,10 +1,22 @@ export class Setting { + static readonly ondemandApiCallModes = { + DEFAULT: 'default', + FORCE: 'force' + } as const + static readonly tokenProperty = 'token' static readonly ondemandApiEnabledProperty = 'ondemand-api-enabled' + static readonly ondemandApiCallModeProperty = 'ondemand-api-call-mode' + static readonly defaultToken = 'sAJGq9JH193KiwnF947v74KnDYkO7z634LWQQfPY' static readonly defaultOndemandApiEnabled = false + static readonly defaultOndemandApiCallMode = Setting.ondemandApiCallModes.DEFAULT - private constructor(private _token, private _ondemandApiEnabled) {} + private constructor( + private _token: string, + private _ondemandApiEnabled: boolean, + private _ondemandApiCallMode: 'default' | 'force' + ) {} public get token(): string { return this._token @@ -22,32 +34,94 @@ export class Setting { this._ondemandApiEnabled = ondemandApiEnabled } + public get ondemandApiCallMode(): 'default' | 'force' { + return this._ondemandApiCallMode + } + + public set ondemandApiCallMode(ondemandApiCallMode: 'default' | 'force') { + this._ondemandApiCallMode = ondemandApiCallMode + } + public setDefaultToken(): void { this._token = Setting.defaultToken } public setDefaultOndemandApiEnabled(): void { - this._ondemandApiEnabled = Setting.defaultOndemandApiEnabled + this.ondemandApiEnabled = Setting.defaultOndemandApiEnabled + } + + public setDefaultOndemandApiCallMode(): void { + this.ondemandApiCallMode = Setting.defaultOndemandApiCallMode } public toObject(): object { return { token: this.token, - ondemandApiEnabled: this.ondemandApiEnabled + ondemandApiEnabled: this.ondemandApiEnabled, + ondemandApiCallMode: this.ondemandApiCallMode } } + public isOndemandApiCallModeDefault(): boolean { + return this.ondemandApiCallMode === Setting.ondemandApiCallModes.DEFAULT + } + + public isOndemandApiCallModeForce(): boolean { + return this.ondemandApiCallMode === Setting.ondemandApiCallModes.FORCE + } + + public isValid(): boolean { + if (!Setting.validOndemanApiCallModes().includes(this.ondemandApiCallMode)) { + return false + } + + if (!this.ondemandApiEnabled && this.ondemandApiCallMode !== Setting.defaultOndemandApiCallMode) { + return false + } + + return true + } + public save(): void { + if (!this.isValid()) { + throw new Error('Setting is invalid state') + } + const props = PropertiesService.getUserProperties() - props.setProperty(Setting.tokenProperty, this._token) - props.setProperty(Setting.ondemandApiEnabledProperty, this._ondemandApiEnabled) + props.setProperty(Setting.tokenProperty, this.token) + props.setProperty(Setting.ondemandApiEnabledProperty, this.ondemandApiEnabled.toString()) + props.setProperty(Setting.ondemandApiCallModeProperty, this.ondemandApiCallMode) + } + + public static castOndemandApiCallModeString(ondemandApiCallModeString: string): 'default' | 'force' { + if (ondemandApiCallModeString === 'default' || ondemandApiCallModeString === 'force') { + return ondemandApiCallModeString + } + + throw new TypeError(`Unsupported ondemandApiCallMode casting: ${ondemandApiCallModeString}`) + } + + public static validOndemanApiCallModes(): string[] { + return Object.values(Setting.ondemandApiCallModes) } public static load(): Setting { const props = PropertiesService.getUserProperties() const token = props.getProperty(this.tokenProperty) const ondemandApiEnabled = props.getProperty(this.ondemandApiEnabledProperty) == 'true' - const setting = new Setting(token, ondemandApiEnabled) + + let ondemandApiCallMode: 'default' | 'force' + try { + ondemandApiCallMode = Setting.castOndemandApiCallModeString(props.getProperty(this.ondemandApiCallModeProperty)) + } catch (e) { + if (e instanceof TypeError) { + ondemandApiCallMode = Setting.defaultOndemandApiCallMode + } else { + throw e + } + } + + const setting = new Setting(token, ondemandApiEnabled, ondemandApiCallMode) return setting } } diff --git a/src/ui/setting-sidebar.html b/src/ui/setting-sidebar.html index acae3b3..77497df 100644 --- a/src/ui/setting-sidebar.html +++ b/src/ui/setting-sidebar.html @@ -3,6 +3,11 @@ + @@ -76,8 +100,16 @@

■注意事項

- - +
diff --git a/src/ui/setting-sidebar.ts b/src/ui/setting-sidebar.ts index eb5fd01..0e7da27 100644 --- a/src/ui/setting-sidebar.ts +++ b/src/ui/setting-sidebar.ts @@ -1,12 +1,16 @@ import { Setting } from '~/setting' export function loadSetting(): object { - return Setting.load().toObject() + const setting = Setting.load() + const obj = setting.toObject() + obj['forceOndemandApiEnabled'] = setting.isOndemandApiCallModeForce() + return obj } -export function saveSetting(token: string, ondemandApiEnabled: boolean): void { +export function saveSetting(token: string, ondemandApiEnabled: boolean, forceOndemandApiEnabled: boolean): void { const setting = Setting.load() setting.token = token setting.ondemandApiEnabled = ondemandApiEnabled + setting.ondemandApiCallMode = forceOndemandApiEnabled ? 'force' : 'default' setting.save() } diff --git a/tsconfig.json b/tsconfig.json index ec60b75..306c044 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "sourceMap": true, "target": "es5", + "lib": ["es2017"], "module": "es2015", "esModuleInterop": true, "incremental": true,