Skip to content

Commit 76d87c5

Browse files
Merge pull request #155 from cubewise-code/feat/issue-154-v12-load-results
Ship v11- and v12-aware }rushti.load.results TI (closes #154)
2 parents 6656f7e + 5d595c2 commit 76d87c5

6 files changed

Lines changed: 315 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22

33
All notable changes to RushTI are documented in this file.
44

5-
## Unreleased
5+
## Unreleased`feat/issue-154-v12-load-results`
66

7+
- Fix: `rushti build` now installs a TM1-version-aware `}rushti.load.results`
8+
TI (closes #154). On v12 targets the body no longer references the removed
9+
`CubeGetLogChanges` / `CubeSetLogChanges` / `ExecuteCommand` functions;
10+
source-file cleanup uses the TM1-native `ASCIIDelete` instead of shelling
11+
out via `cmd /c del`. The v11 body is unchanged.
712
- **Per-workflow `tm1_instance` setting** for results push and auto-load.
813
Set it inside a JSON taskfile's `settings` block to override the
914
`settings.ini` default per workflow. Resolution chain (highest wins):

src/rushti/tm1_build.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,19 @@
3232
Process,
3333
Subset,
3434
)
35+
from TM1py.Utils import integerize_version
3536

3637
from rushti.tm1_objects import (
3738
MEASURE_ATTRIBUTES,
3839
MEASURE_ELEMENTS,
3940
PROCESS_DATA,
4041
PROCESS_DATASOURCE,
41-
PROCESS_EPILOG,
42+
PROCESS_EPILOG_V11,
43+
PROCESS_EPILOG_V12,
4244
PROCESS_METADATA,
4345
PROCESS_PARAMETERS,
44-
PROCESS_PROLOG,
46+
PROCESS_PROLOG_V11,
47+
PROCESS_PROLOG_V12,
4548
PROCESS_VARIABLES,
4649
RUN_ID_SEED_ELEMENTS,
4750
SAMPLE_DATA,
@@ -558,12 +561,16 @@ def _create_process(tm1: TM1Service, process_name: str, force: bool = False) ->
558561
logger.info(f"Replacing existing process: {process_name}")
559562
tm1.processes.delete(process_name)
560563

564+
is_v12 = integerize_version(tm1.version, 2) >= 12
565+
prolog = PROCESS_PROLOG_V12 if is_v12 else PROCESS_PROLOG_V11
566+
epilog = PROCESS_EPILOG_V12 if is_v12 else PROCESS_EPILOG_V11
567+
561568
process = Process(
562569
name=process_name,
563-
prolog_procedure=PROCESS_PROLOG,
570+
prolog_procedure=prolog,
564571
metadata_procedure=PROCESS_METADATA,
565572
data_procedure=PROCESS_DATA,
566-
epilog_procedure=PROCESS_EPILOG,
573+
epilog_procedure=epilog,
567574
datasource_type=PROCESS_DATASOURCE["Type"],
568575
datasource_ascii_decimal_separator=PROCESS_DATASOURCE["asciiDecimalSeparator"],
569576
datasource_ascii_delimiter_char=PROCESS_DATASOURCE["asciiDelimiterChar"],

src/rushti/tm1_objects.py

Lines changed: 181 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@
1010
- TASK_ID_ELEMENT_COUNT: Number of elements to generate for task_id dimension (1..N)
1111
- RUN_ID_SEED_ELEMENTS: Seed elements for run_id dimension
1212
- WORKFLOW_SEED_ELEMENTS: Seed elements for workflow dimension
13-
- PROCESS_DEFINITION: Full TI process definition for }rushti.load.results
13+
- PROCESS_PROLOG_V11 / PROCESS_PROLOG_V12: Prolog body per TM1 major version
14+
- PROCESS_EPILOG_V11 / PROCESS_EPILOG_V12: Epilog body per TM1 major version
1415
- SAMPLE_DATA: Sample cube data for demonstration taskfiles
16+
17+
The v12 variants drop calls to functions that were removed or are unsupported
18+
on TM1 v12 (``CubeGetLogChanges`` / ``CubeSetLogChanges`` and
19+
``ExecuteCommand``). Source-file cleanup on v12 is handled by the TM1-native
20+
``ASCIIDelete`` instead of shelling out to ``cmd /c del``.
1521
"""
1622

1723
# ---------------------------------------------------------------------------
@@ -85,7 +91,7 @@
8591
# TI Process: }rushti.load.results
8692
# ---------------------------------------------------------------------------
8793

88-
PROCESS_PROLOG = r"""#################################################################################################
94+
PROCESS_PROLOG_V11 = r"""#################################################################################################
8995
##~~Join the bedrock TM1 community on GitHub https://github.com/cubewise-code/bedrock Ver 4.0~~##
9096
#################################################################################################
9197
@@ -208,6 +214,142 @@
208214
#Region - DataSource
209215
210216
217+
### Assign data source
218+
If( nErrors = 0 );
219+
DatasourceType = 'CHARACTERDELIMITED';
220+
DatasourceNameForServer = cFileSrc;
221+
DatasourceASCIIHeaderRecords = 1;
222+
DatasourceASCIIDelimiter=',';
223+
DatasourceASCIIDecimalSeparator='.';
224+
DatasourceASCIIThousandSeparator='';
225+
EndIf;
226+
227+
#EndRegion - DataSource
228+
#################################################################################################
229+
"""
230+
231+
# v12 prolog: identical to v11 except the CubeGetLogChanges / CubeSetLogChanges
232+
# block is removed. Those functions are unsupported on TM1 v12 and would cause
233+
# the TI to fail at compile time.
234+
PROCESS_PROLOG_V12 = r"""#################################################################################################
235+
##~~Join the bedrock TM1 community on GitHub https://github.com/cubewise-code/bedrock Ver 4.0~~##
236+
#################################################################################################
237+
238+
#****Begin: Generated Statements***
239+
#****End: Generated Statements****
240+
241+
#################################################################################################
242+
### CHANGE HISTORY:
243+
### MODIFICATION DATE CHANGED BY COMMENT
244+
### 2026-01-15 Nicolas Bisurgi Creation of Process
245+
### YYYY-MM-DD Developer Name Reason for modification here
246+
#################################################################################################
247+
#Region @DOC
248+
# Description:
249+
# This process will load RushTI result files into a TM1 cube
250+
251+
# Use case:
252+
# Storing RushTI execution stats in a TM1 Cube
253+
254+
# Note:
255+
# * This process assumes a there is a rushti compliant cube and dimensions. If you don't have it
256+
# * please run: rushti.py build --tm1-instance tm1srv01
257+
#EndRegion @DOC
258+
#################################################################################################
259+
260+
#################################################################################################
261+
#Region Process Declarations
262+
### Process Parameters
263+
# a short description of what the process does goes here in cAction variable, e.g. "copied data from cube A to cube B". This will be written to the message log if pLogOutput=1
264+
cAction = 'loading rushti stats into ' | pTargetCube;
265+
cParamArray = '';
266+
# to use the parameter array remove the line above and uncomment the line below, adding the needed parameters in the provided format
267+
#cParamArray = 'pLogOutput:%pLogOutput%, pTemp:%pTemp%';
268+
269+
### Global Variables
270+
StringGlobalVariable('sProcessReturnCode');
271+
NumericGlobalVariable('nProcessReturnCode');
272+
273+
### Standard Constants
274+
cThisProcName = GetProcessName();
275+
cUserName = TM1User();
276+
cTimeStamp = TimSt( Now, '\Y\m\d\h\i\s' );
277+
cRandomInt = NumberToString( INT( RAND( ) * 1000 ));
278+
cTempObjName = Expand('%cThisProcName%_%cTimeStamp%_%cRandomInt%');
279+
cViewClr = '}bedrock_clear_' | cTempObjName;
280+
cViewSrc = '}bedrock_source_' | cTempObjName;
281+
cMsgErrorLevel = 'ERROR';
282+
cMsgErrorContent = 'Process:%cThisProcName% ErrorMsg:%sMessage%';
283+
cLogInfo = 'Process:%cThisProcName% run with parameters %cParamArray%';
284+
sDelimEleStart = '¦';
285+
sDelimDim = '&';
286+
sDelimEle = '+';
287+
nProcessReturnCode = 0;
288+
nErrors = 0;
289+
nMetadataRecordCount= 0;
290+
nDataRecordCount = 0;
291+
292+
### Process Specific Constants
293+
cFileSrc = pSourceFile;
294+
cCubeTgt = pTargetCube;
295+
296+
#EndRegion Process Declarations
297+
#################################################################################################
298+
299+
### LogOutput parameters
300+
IF( pLogoutput = 1 );
301+
LogOutput('INFO', Expand( cLogInfo ) );
302+
ENDIF;
303+
304+
#################################################################################################
305+
#Region Validate Parameters
306+
307+
# pLogOutput
308+
If( pLogOutput >= 1 );
309+
pLogOutput = 1;
310+
Else;
311+
pLogOutput = 0;
312+
EndIf;
313+
314+
# Validate source file
315+
If( Trim( cFileSrc ) @= '' );
316+
nErrors = nErrors + 1;
317+
sMessage = 'No source cfileube specified.';
318+
LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
319+
ElseIf( FileExists( cFileSrc ) = 0 );
320+
nErrors = nErrors + 1;
321+
sMessage = Expand( 'Invalid source file specified: %cFileSrc%.');
322+
LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
323+
EndIf;
324+
325+
# Validate target cube
326+
If( Trim( cCubeTgt ) @= '' );
327+
nErrors = nErrors + 1;
328+
sMessage = 'No target cube specified.';
329+
LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
330+
ElseIf( CubeExists( cCubeTgt ) = 0 );
331+
nErrors = nErrors + 1;
332+
sMessage = Expand( 'Invalid target cube specified: %cCubeTgt%.');
333+
LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
334+
EndIf;
335+
336+
# If any parameters fail validation then set data source of process to null and go directly to epilog
337+
If( nErrors > 0 );
338+
DataSourceType = 'NULL';
339+
If( pStrictErrorHandling = 1 );
340+
ProcessQuit;
341+
Else;
342+
ProcessBreak;
343+
EndIf;
344+
EndIf;
345+
346+
#################################################################################################
347+
#EndRegion Validate Parameters
348+
349+
#################################################################################################
350+
#Region - DataSource
351+
352+
211353
### Assign data source
212354
If( nErrors = 0 );
213355
DatasourceType = 'CHARACTERDELIMITED';
@@ -271,7 +413,7 @@
271413
CellPutS(voriginal_task_id, pTargetCube, vworkflow, vrun_id, vtask_id, 'original_task_id');
272414
"""
273415

274-
PROCESS_EPILOG = r"""#################################################################################################
416+
PROCESS_EPILOG_V11 = r"""#################################################################################################
275417
##~~Join the bedrock TM1 community on GitHub https://github.com/cubewise-code/bedrock Ver 4.0~~##
276418
#################################################################################################
277419
@@ -308,6 +450,42 @@
308450
309451
### End Epilog ###"""
310452

453+
# v12 epilog: source-file cleanup uses TM1-native ASCIIDelete (no shell-out),
454+
# and the CubeSetLogChanges restore is omitted because the v12 prolog does not
455+
# toggle transaction logging.
456+
PROCESS_EPILOG_V12 = r"""#################################################################################################
457+
##~~Join the bedrock TM1 community on GitHub https://github.com/cubewise-code/bedrock Ver 4.0~~##
458+
#################################################################################################
459+
460+
#****Begin: Generated Statements***
461+
#****End: Generated Statements****
462+
463+
### If required delete the source file
464+
if(pDeleteSourceFile=1);
465+
Sleep(1000);
466+
ASCIIDelete(pSourceFile);
467+
endif;
468+
469+
### Return code & final error message handling
470+
If( nErrors > 0 );
471+
sMessage = 'the process incurred at least 1 error. Please see above lines in this file for more details.';
472+
nProcessReturnCode = 0;
473+
LogOutput( cMsgErrorLevel, Expand( cMsgErrorContent ) );
474+
sProcessReturnCode = Expand( '%sProcessReturnCode% Process:%cThisProcName% completed with errors. Check tm1server.log for details.' );
475+
If( pStrictErrorHandling = 1 );
476+
ProcessQuit;
477+
EndIf;
478+
Else;
479+
sProcessAction = Expand( 'Process:%cThisProcName% successfully %cAction%. %nDataRecordCount% records processed.' );
480+
sProcessReturnCode = Expand( '%sProcessReturnCode% %sProcessAction%' );
481+
nProcessReturnCode = 1;
482+
If( pLogoutput = 1 );
483+
LogOutput('INFO', Expand( sProcessAction ) );
484+
EndIf;
485+
EndIf;
486+
487+
### End Epilog ###"""
488+
311489
PROCESS_PARAMETERS = [
312490
{
313491
"Name": "pSourceFile",

tests/integration/test_v11_v12_results.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,62 @@ def test_execute_with_return(self):
133133
)
134134
self.assertTrue(success)
135135

136+
def test_auto_load_results(self):
137+
"""Push CSV then call }rushti.load.results on v11 (legacy body path)."""
138+
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
139+
db_path = f.name
140+
141+
stats_db = None
142+
try:
143+
stats_db = StatsDatabase(db_path=db_path, enabled=True)
144+
run_id = "test_autoload_v11_" + datetime.now().strftime("%Y%m%d_%H%M%S")
145+
workflow = "test-autoload-v11"
146+
147+
stats_db.start_run(run_id=run_id, workflow=workflow)
148+
stats_db.record_task(
149+
run_id=run_id,
150+
task_id="task1",
151+
instance=self.INSTANCE,
152+
process="}bedrock.server.wait",
153+
parameters={"pWaitSec": "1"},
154+
success=True,
155+
start_time=datetime.now(),
156+
end_time=datetime.now() + timedelta(seconds=1),
157+
retry_count=0,
158+
error_message=None,
159+
workflow=workflow,
160+
)
161+
stats_db.complete_run(run_id=run_id, success_count=1, failure_count=0)
162+
163+
results_df = build_results_dataframe(stats_db, workflow, run_id)
164+
file_name = upload_results_to_tm1(self.tm1, workflow, run_id, results_df)
165+
166+
if not self.tm1.processes.exists("}rushti.load.results"):
167+
self.skipTest("}rushti.load.results process not found on TM1 instance")
168+
169+
# v11 file paths need .blb to be locatable by the TI's CHARACTERDELIMITED source.
170+
version = integerize_version(self.tm1.version, 2)
171+
load_file_name = file_name + ".blb" if version < 12 else file_name
172+
173+
success, status, error_log = self.tm1.processes.execute_with_return(
174+
"}rushti.load.results",
175+
pSourceFile=load_file_name,
176+
pTargetCube="rushti",
177+
)
178+
self.assertTrue(
179+
success,
180+
f"}}rushti.load.results failed on v11 (status={status}, error_log={error_log})",
181+
)
182+
183+
try:
184+
self.tm1.files.delete(file_name)
185+
except Exception:
186+
pass
187+
finally:
188+
if stats_db is not None:
189+
stats_db.close()
190+
os.unlink(db_path)
191+
136192

137193
@pytest.mark.requires_tm1
138194
@pytest.mark.integration

tests/integration/tm1_setup.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,22 +87,29 @@ def ensure_load_results_process(
8787
return False
8888

8989
from TM1py.Objects import Process
90+
from TM1py.Utils import integerize_version
9091
from rushti.tm1_objects import (
9192
PROCESS_DATA,
9293
PROCESS_DATASOURCE,
93-
PROCESS_EPILOG,
94+
PROCESS_EPILOG_V11,
95+
PROCESS_EPILOG_V12,
9496
PROCESS_METADATA,
9597
PROCESS_PARAMETERS,
96-
PROCESS_PROLOG,
98+
PROCESS_PROLOG_V11,
99+
PROCESS_PROLOG_V12,
97100
PROCESS_VARIABLES,
98101
)
99102

103+
is_v12 = integerize_version(tm1.version, 2) >= 12
104+
prolog = PROCESS_PROLOG_V12 if is_v12 else PROCESS_PROLOG_V11
105+
epilog = PROCESS_EPILOG_V12 if is_v12 else PROCESS_EPILOG_V11
106+
100107
process = Process(
101108
name=process_name,
102-
prolog_procedure=PROCESS_PROLOG,
109+
prolog_procedure=prolog,
103110
metadata_procedure=PROCESS_METADATA,
104111
data_procedure=PROCESS_DATA,
105-
epilog_procedure=PROCESS_EPILOG,
112+
epilog_procedure=epilog,
106113
datasource_type=PROCESS_DATASOURCE["Type"],
107114
datasource_ascii_decimal_separator=PROCESS_DATASOURCE["asciiDecimalSeparator"],
108115
datasource_ascii_delimiter_char=PROCESS_DATASOURCE["asciiDelimiterChar"],

0 commit comments

Comments
 (0)