Skip to content

Commit 552557b

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(oracle-epm-planning): validate approval confirmation routes
1 parent fc6c6bd commit 552557b

32 files changed

Lines changed: 586 additions & 333 deletions

apps/docs/content/docs/integrations/oracle_epm_planning.mdx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,6 @@ Retrieve IPM insights using the insight-specific slice. Defaults to existing ins
939939
|`priority` | string | priority |
940940
|`pov` | string | pov |
941941
|`percentageDiff` | string | percentageDiff |
942-
|`anomalyPeriod` | string | anomalyPeriod |
943942
|`percentageDiffFromAnomaly` | string | percentageDiffFromAnomaly |
944943
|`outlierValue` | number | Raw anomaly outlier |
945944
|`actualImpactValue` | number | Raw impact value |

apps/sim/blocks/blocks/oracle_epm_planning.ts

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,11 @@ function operationParams(params: Record<string, unknown>): Record<string, unknow
243243
result.oauthCredential = params.oauthCredential
244244
for (const [wire, field] of Object.entries(fields)) {
245245
if (params.operation === 'oracle_epm_planning_summarize_insights') {
246-
if (params.summaryInputMode === 'ids' &&
247-
['cube', 'insightSlice', 'retrievalMode', 'calendar'].includes(wire)) continue
246+
if (
247+
params.summaryInputMode === 'ids' &&
248+
['cube', 'insightSlice', 'retrievalMode', 'calendar'].includes(wire)
249+
)
250+
continue
248251
if (params.summaryInputMode === 'slice' && wire === 'insightIds') continue
249252
}
250253
if (wire === 'calendar' && params.retrievalMode !== 'FORCE_RECOMPUTE') continue
@@ -266,11 +269,14 @@ function operationParams(params: Record<string, unknown>): Record<string, unknow
266269
min: wire === 'offset' ? 0 : 1,
267270
max: wire === 'offset' ? 1_000_000 : wire === 'limit' ? 1000 : 3600,
268271
})
269-
} else if (['actionId', 'approvalOptions', 'annotSeq', 'logSeq', 'summarySize'].includes(wire)) {
272+
} else if (
273+
['actionId', 'approvalOptions', 'annotSeq', 'logSeq', 'summarySize'].includes(wire)
274+
) {
270275
result[wire] = parseOptionalNumberInput(value, field, {
271276
integer: true,
272277
min: wire === 'annotSeq' || wire === 'logSeq' ? -1 : wire === 'approvalOptions' ? 0 : 1,
273-
max: wire === 'approvalOptions' ? 1 : wire === 'summarySize' ? 10000 : Number.MAX_SAFE_INTEGER,
278+
max:
279+
wire === 'approvalOptions' ? 1 : wire === 'summarySize' ? 10000 : Number.MAX_SAFE_INTEGER,
274280
})
275281
} else if (wire === 'jobId' && typeof value === 'number') {
276282
if (!Number.isSafeInteger(value) || value < 0)
@@ -840,7 +846,10 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
840846
{ label: 'List Planning Units', id: 'oracle_epm_planning_list_planning_units' },
841847
{ label: 'Get Planning Unit Actions', id: 'oracle_epm_planning_get_planning_unit_actions' },
842848
{ label: 'Get Planning Unit History', id: 'oracle_epm_planning_get_planning_unit_history' },
843-
{ label: 'Change Planning Unit Status', id: 'oracle_epm_planning_change_planning_unit_status' },
849+
{
850+
label: 'Change Planning Unit Status',
851+
id: 'oracle_epm_planning_change_planning_unit_status',
852+
},
844853
{ label: 'Get Insights', id: 'oracle_epm_planning_get_insights' },
845854
{ label: 'Summarize Insights', id: 'oracle_epm_planning_summarize_insights' },
846855
],
@@ -1025,7 +1034,9 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
10251034
field: 'operation',
10261035
value: [
10271036
'oracle_epm_planning_get_insights',
1028-
...(values?.summaryInputMode === 'slice' ? ['oracle_epm_planning_summarize_insights'] : []),
1037+
...(values?.summaryInputMode === 'slice'
1038+
? ['oracle_epm_planning_summarize_insights']
1039+
: []),
10291040
'oracle_epm_planning_list_dimensions',
10301041
'oracle_epm_planning_get_dimension',
10311042
'oracle_epm_planning_list_substitution_variables',
@@ -1043,7 +1054,9 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
10431054
required: (values) => ({
10441055
field: 'operation',
10451056
value: [
1046-
...(values?.summaryInputMode === 'slice' ? ['oracle_epm_planning_summarize_insights'] : []),
1057+
...(values?.summaryInputMode === 'slice'
1058+
? ['oracle_epm_planning_summarize_insights']
1059+
: []),
10471060
'oracle_epm_planning_get_insights',
10481061
'oracle_epm_planning_list_dimensions',
10491062
'oracle_epm_planning_get_dimension',
@@ -1067,7 +1080,9 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
10671080
field: 'operation',
10681081
value: [
10691082
'oracle_epm_planning_get_insights',
1070-
...(values?.summaryInputMode === 'slice' ? ['oracle_epm_planning_summarize_insights'] : []),
1083+
...(values?.summaryInputMode === 'slice'
1084+
? ['oracle_epm_planning_summarize_insights']
1085+
: []),
10711086
'oracle_epm_planning_list_dimensions',
10721087
'oracle_epm_planning_get_dimension',
10731088
'oracle_epm_planning_list_substitution_variables',
@@ -1085,7 +1100,9 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
10851100
required: (values) => ({
10861101
field: 'operation',
10871102
value: [
1088-
...(values?.summaryInputMode === 'slice' ? ['oracle_epm_planning_summarize_insights'] : []),
1103+
...(values?.summaryInputMode === 'slice'
1104+
? ['oracle_epm_planning_summarize_insights']
1105+
: []),
10891106
'oracle_epm_planning_get_insights',
10901107
'oracle_epm_planning_list_dimensions',
10911108
'oracle_epm_planning_get_dimension',
@@ -1656,10 +1673,12 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
16561673
id: 'objectType',
16571674
title: 'Data Map Job Type',
16581675
type: 'dropdown',
1659-
options: [{
1660-
label: 'Plan Type Map',
1661-
id: 'PLAN_TYPE_MAP',
1662-
}],
1676+
options: [
1677+
{
1678+
label: 'Plan Type Map',
1679+
id: 'PLAN_TYPE_MAP',
1680+
},
1681+
],
16631682
defaultValue: 'PLAN_TYPE_MAP',
16641683
condition: {
16651684
field: 'operation',
@@ -1713,7 +1732,8 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
17131732
field: 'operation',
17141733
value: ['oracle_epm_planning_run_data_map'],
17151734
},
1716-
placeholder: 'Explicitly clear the target region before copying. Destructive when true; Sim defaults to false, while Oracle defaults to true.',
1735+
placeholder:
1736+
'Explicitly clear the target region before copying. Destructive when true; Sim defaults to false, while Oracle defaults to true.',
17171737
defaultValue: false,
17181738
},
17191739
{
@@ -1725,12 +1745,14 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
17251745
value: ['oracle_epm_planning_run_data_map'],
17261746
},
17271747
required: false,
1728-
placeholder: 'Optional dimension-to-member-selection map, for example {"Period":"ILvl0Descendants(Q1)"}. Values must be strings.',
1748+
placeholder:
1749+
'Optional dimension-to-member-selection map, for example {"Period":"ILvl0Descendants(Q1)"}. Values must be strings.',
17291750
language: 'json',
17301751
canvasNoun: 'member selections',
17311752
wandConfig: {
17321753
enabled: true,
1733-
prompt: 'Generate only the requested Oracle Planning JSON using supplied tenant names. Optional dimension-to-member-selection map, for example {"Period":"ILvl0Descendants(Q1)"}. Values must be strings. Return ONLY JSON; do not invent names or unsupported fields.',
1754+
prompt:
1755+
'Generate only the requested Oracle Planning JSON using supplied tenant names. Optional dimension-to-member-selection map, for example {"Period":"ILvl0Descendants(Q1)"}. Values must be strings. Return ONLY JSON; do not invent names or unsupported fields.',
17341756
placeholder: 'Describe the requested overrideMembersMap',
17351757
},
17361758
mode: 'advanced',
@@ -1744,12 +1766,14 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
17441766
value: ['oracle_epm_planning_run_data_map'],
17451767
},
17461768
required: false,
1747-
placeholder: 'Optional dimension-to-excluded-member-selection map, for example {"Period":"Jan"}. Values must be strings.',
1769+
placeholder:
1770+
'Optional dimension-to-excluded-member-selection map, for example {"Period":"Jan"}. Values must be strings.',
17481771
language: 'json',
17491772
canvasNoun: 'member selections',
17501773
wandConfig: {
17511774
enabled: true,
1752-
prompt: 'Generate only the requested Oracle Planning JSON using supplied tenant names. Optional dimension-to-excluded-member-selection map, for example {"Period":"Jan"}. Values must be strings. Return ONLY JSON; do not invent names or unsupported fields.',
1775+
prompt:
1776+
'Generate only the requested Oracle Planning JSON using supplied tenant names. Optional dimension-to-excluded-member-selection map, for example {"Period":"Jan"}. Values must be strings. Return ONLY JSON; do not invent names or unsupported fields.',
17531777
placeholder: 'Describe the requested overrideExclusionMembersMap',
17541778
},
17551779
mode: 'advanced',
@@ -1766,12 +1790,14 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
17661790
field: 'operation',
17671791
value: ['oracle_epm_planning_set_user_variable_values'],
17681792
},
1769-
placeholder: '1–1000 user-variable values: [{userName, name, dimension, member}]. Names are tenant-specific; do not assume batch atomicity.',
1793+
placeholder:
1794+
'1–1000 user-variable values: [{userName, name, dimension, member}]. Names are tenant-specific; do not assume batch atomicity.',
17701795
language: 'json',
17711796
canvasNoun: 'user-variable values',
17721797
wandConfig: {
17731798
enabled: true,
1774-
prompt: 'Generate only the requested Oracle Planning JSON using supplied tenant names. 1–1000 user-variable values: [{userName, name, dimension, member}]. Names are tenant-specific; do not assume batch atomicity. Return ONLY JSON; do not invent names or unsupported fields.',
1799+
prompt:
1800+
'Generate only the requested Oracle Planning JSON using supplied tenant names. 1–1000 user-variable values: [{userName, name, dimension, member}]. Names are tenant-specific; do not assume batch atomicity. Return ONLY JSON; do not invent names or unsupported fields.',
17751801
placeholder: 'Describe the requested userVariableValues',
17761802
},
17771803
},
@@ -1809,13 +1835,20 @@ export const OracleEpmPlanningBlock: BlockConfig<OracleEpmPlanningResponse> = {
18091835
type: 'short-input',
18101836
condition: {
18111837
field: 'operation',
1812-
value: ['oracle_epm_planning_get_planning_unit_actions', 'oracle_epm_planning_change_planning_unit_status'],
1838+
value: [
1839+
'oracle_epm_planning_get_planning_unit_actions',
1840+
'oracle_epm_planning_change_planning_unit_status',
1841+
],
18131842
},
18141843
required: {
18151844
field: 'operation',
1816-
value: ['oracle_epm_planning_get_planning_unit_actions', 'oracle_epm_planning_change_planning_unit_status'],
1845+
value: [
1846+
'oracle_epm_planning_get_planning_unit_actions',
1847+
'oracle_epm_planning_change_planning_unit_status',
1848+
],
18171849
},
1818-
placeholder: 'Raw Oracle planning-unit hierarchy identifier for scenario and version, including required quotes and :: separators. Not a numeric puId or a URL. Maximum 255 UTF-8 bytes; do not percent-encode.',
1850+
placeholder:
1851+
'Raw Oracle planning-unit hierarchy identifier for scenario and version, including required quotes and :: separators. Not a numeric puId or a URL. Maximum 255 UTF-8 bytes; do not percent-encode.',
18191852
},
18201853
{
18211854
id: 'puIdentifier',

apps/sim/lib/internal/oracle-epm-planning/execute-tool.test.ts

Lines changed: 42 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -843,9 +843,7 @@ const CASES: {
843843
],
844844
},
845845
output: 'planningUnits',
846-
form: {
847-
848-
},
846+
form: {},
849847
query: {
850848
q: '{"scenario":"Forecast","version":"Working"}',
851849
offset: '5',
@@ -956,37 +954,16 @@ const CASES: {
956954
cube: 'Plan1',
957955
insightSlice: {
958956
pov: {
959-
members: [
960-
'Sales',
961-
],
962-
dimensions: [
963-
'Account',
964-
],
957+
members: ['Sales'],
958+
dimensions: ['Account'],
965959
},
966960
columnAxisDefinition: {
967-
dimensions: [
968-
'Period',
969-
],
970-
segments: [
971-
[
972-
[
973-
'Jan',
974-
'Feb',
975-
],
976-
],
977-
],
961+
dimensions: ['Period'],
962+
segments: [[['Jan', 'Feb']]],
978963
},
979964
rowAxisDefinition: {
980-
dimensions: [
981-
'Scenario',
982-
],
983-
segments: [
984-
[
985-
[
986-
'Forecast',
987-
],
988-
],
989-
],
965+
dimensions: ['Scenario'],
966+
segments: [[['Forecast']]],
990967
},
991968
},
992969
},
@@ -1012,37 +989,16 @@ const CASES: {
1012989
location: 'Plan1',
1013990
slice: {
1014991
pov: {
1015-
members: [
1016-
'Sales',
1017-
],
1018-
dimensions: [
1019-
'Account',
1020-
],
992+
members: ['Sales'],
993+
dimensions: ['Account'],
1021994
},
1022995
columnAxisDefinition: {
1023-
dimensions: [
1024-
'Period',
1025-
],
1026-
segments: [
1027-
[
1028-
[
1029-
'Jan',
1030-
'Feb',
1031-
],
1032-
],
1033-
],
996+
dimensions: ['Period'],
997+
segments: [[['Jan', 'Feb']]],
1034998
},
1035999
rowAxisDefinition: {
1036-
dimensions: [
1037-
'Scenario',
1038-
],
1039-
segments: [
1040-
[
1041-
[
1042-
'Forecast',
1043-
],
1044-
],
1045-
],
1000+
dimensions: ['Scenario'],
1001+
segments: [[['Forecast']]],
10461002
},
10471003
},
10481004
retrievalMode: 'USE_EXISTING',
@@ -1054,9 +1010,7 @@ const CASES: {
10541010
input: {
10551011
application: 'Vision',
10561012
summaryInputMode: 'ids',
1057-
insightIds: [
1058-
'426',
1059-
],
1013+
insightIds: ['426'],
10601014
},
10611015
method: 'POST',
10621016
path: '/HyperionPlanning/rest/v3/applications/Vision/insights/summary',
@@ -1067,9 +1021,7 @@ const CASES: {
10671021
body: {
10681022
format: 'text',
10691023
size: 100,
1070-
ids: [
1071-
'426',
1072-
],
1024+
ids: ['426'],
10731025
},
10741026
source: 'insigh_summ.html',
10751027
},
@@ -1188,22 +1140,38 @@ describe('Planning operation contracts through the real foundation', () => {
11881140
it('requires explicit data-map clearing without widening generic job parameters', async () => {
11891141
for (const [operation, input] of [
11901142
['run_data_map', { application: 'Vision', jobName: 'Reporting' }],
1191-
['run_data_map', { application: 'Vision', jobName: 'Reporting', clearData: false, overrideMembersMap: { Period: ['Jan'] } }],
1192-
['run_job', { application: 'Vision', jobType: 'PLAN_TYPE_MAP', jobName: 'Reporting', parameters: { overrideMembersMap: { Period: 'Jan' } } }],
1143+
[
1144+
'run_data_map',
1145+
{
1146+
application: 'Vision',
1147+
jobName: 'Reporting',
1148+
clearData: false,
1149+
overrideMembersMap: { Period: ['Jan'] },
1150+
},
1151+
],
1152+
[
1153+
'run_job',
1154+
{
1155+
application: 'Vision',
1156+
jobType: 'PLAN_TYPE_MAP',
1157+
jobName: 'Reporting',
1158+
parameters: { overrideMembersMap: { Period: 'Jan' } },
1159+
},
1160+
],
11931161
] as const) {
11941162
expect((await invoke(operation, input)).status).toBe(400)
11951163
}
11961164
expect(mocks.fetch).not.toHaveBeenCalled()
11971165
})
1198-
it.each([400, 403])('does not hide or replay a user-variable batch error (%i)', async (status) => {
1199-
respond({ items: [{ id: 'member', details: 'Invalid selection' }] }, status)
1200-
const entry = CASES.find((item) => item.operation === 'set_user_variable_values')!
1201-
const result = await invoke(entry.operation, entry.input)
1202-
expect(result.status).toBe(status)
1203-
expect(result.result.success).toBe(false)
1204-
expect(result.result.output.updated).toBeUndefined()
1205-
expect(mocks.fetch).toHaveBeenCalledTimes(1)
1206-
})
1166+
it.each([400, 403])(
1167+
'does not hide or replay a user-variable batch error (%i)',
1168+
async (status) => {
1169+
respond({ items: [{ id: 'member', details: 'Invalid selection' }] }, status)
1170+
const entry = CASES.find((item) => item.operation === 'set_user_variable_values')!
1171+
const result = await invoke(entry.operation, entry.input)
1172+
expect(result.status).toBe(status)
1173+
expect(result.result.success).toBe(false)
1174+
expect(result.result.output.updated).toBeUndefined()
12071175
it('accepts only empty 204 as user-variable write confirmation', async () => {
12081176
const entry = CASES.find((item) => item.operation === 'set_user_variable_values')!
12091177
respond(null, 204)

0 commit comments

Comments
 (0)