Skip to content

Commit 81d6143

Browse files
committed
added example of finding ccm workitem
1 parent 10670e6 commit 81d6143

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+17182
-16925
lines changed

elmclient/_ccm.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,45 @@ def is_type_uri(self, uri):
251251
return False
252252

253253
def resolve_uri_to_name(self, uri, trytouseasid=False):
254-
return self.__super__.resolve_uri_to_name(self, uri, trytouseasid=True)
254+
logger.info( f"resolve_uri_to_name {uri=}" )
255+
if not uri:
256+
result = None
257+
return result
258+
if not uri.startswith('http://') or not uri.startswith('https://'):
259+
# try to remove prefix
260+
uri1 = rdfxml.tag_to_uri(uri,noexception=True)
261+
logger.debug(f"Trying to remove prefix {uri=} {uri1=}")
262+
if uri1 is None:
263+
return uri
264+
if uri1 != uri:
265+
logger.debug( f"Changed {uri} to {uri1}" )
266+
else:
267+
logger.debug( f"NOT Changed {uri} to {uri1}" )
268+
# use the transformed URI
269+
uri = uri1
270+
if not uri.startswith(self.app.baseurl):
271+
if self.server.jts.is_user_uri(uri):
272+
result = self.server.jts.user_uritoname_resolver(uri)
273+
logger.debug(f"returning user")
274+
return result
275+
uri1 = rdfxml.uri_to_prefixed_tag(uri,noexception=True)
276+
logger.debug(f"No app base URL {self.app.baseurl=} {uri=} {uri1=}")
277+
return uri1
278+
elif not self.is_known_uri(uri):
279+
if self.server.jts.is_user_uri(uri):
280+
result = self.server.jts.user_uritoname_resolver(uri)
281+
else:
282+
if uri.startswith( "http://" ) or uri.startswith( "https://" ):
283+
uri1 = rdfxml.uri_to_prefixed_tag(uri)
284+
logger.debug( f"Returning the raw URI {uri} so changed it to prefixed {uri1}" )
285+
uri = uri1
286+
result = uri
287+
# ensure the result is in the types cache, in case it recurrs the result can be pulled from the cache
288+
self.register_name(result,uri)
289+
else:
290+
result = self.get_uri_name(uri)
291+
logger.info( f"Result {result=}" )
292+
return result
255293

256294
#################################################################################################
257295

elmclient/_project.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def report_type_system( self ):
9393
shortname += " (default)" if self.app.default_query_resource is not None and k==rdfxml.tag_to_uri(self.app.default_query_resource) else ""
9494
rows.append( [shortname,k,app_qcdetails[k]])
9595
# print in a nice table with equal length columns
96+
print( f"{rows=}" )
9697
report += utils.print_in_html(rows,['Short Name', 'URI', 'Query Capability URI'])
9798

9899
report += "<H2>Project Queryable Resource Types, short name and URI</H2>\n"

elmclient/_rm.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,7 @@ def deliver_changeset( self ):
10661066
#################################################################################################
10671067

10681068
@utils.mixinomatic
1069-
class _RMApp(_app._App, _typesystem.No_Type_System_Mixin):
1069+
class _RMApp(_app._App, oslcqueryapi._OSLCOperations_Mixin, _typesystem.Type_System_Mixin):
10701070
domain = 'rm'
10711071
project_class = _RMProject
10721072
supports_configs = True
@@ -1116,6 +1116,40 @@ def _get_headers(self, headers=None):
11161116
logger.info( f"rm gh {result=}" )
11171117
return result
11181118

1119+
# load the typesystem using the OSLC shape resources listed for all the creation factories and query capabilities
1120+
def load_types(self, force=False):
1121+
self._load_types(force)
1122+
1123+
# load the typesystem using the OSLC shape resources
1124+
def _load_types(self,force=False):
1125+
logger.debug( f"load type {self=} {force=}" )
1126+
1127+
# if already loaded, try to avoid reloading
1128+
if self.typesystem_loaded and not force:
1129+
return
1130+
1131+
self.clear_typesystem()
1132+
1133+
# get the services.xml
1134+
sx = self.retrieve_oslc_catalog_xml()
1135+
if sx:
1136+
shapes_to_load = rdfxml.xml_find_elements(sx, './/oslc:resourceShape')
1137+
print( f"{shapes_to_load=}" )
1138+
burp
1139+
1140+
pbar = tqdm.tqdm(initial=0, total=len(shapes_to_load),smoothing=1,unit=" results",desc="Loading ERM/DN shapes")
1141+
1142+
for el in shapes_to_load:
1143+
self._load_type_from_resource_shape(el)
1144+
pbar.update(1)
1145+
1146+
pbar.close()
1147+
else:
1148+
raise Exception( "services xml not found!" )
1149+
1150+
self.typesystem_loaded = True
1151+
return None
1152+
11191153
@classmethod
11201154
def add_represt_arguments( cls, subparsers, common_args ):
11211155
'''

elmclient/examples/OSLCQUERY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Of course there are things about a query that can't be elided - you still have t
2626
Overview - OSLC Query
2727
=====================
2828

29-
For full details on the OSLC Query specification see https://docs.oasis-open.org/oslc-core/oslc-query/v3.0/oslc-query-v3.0.html
29+
For full details on the OSLC Query specification see https://docs.oasis-open-projects.org/oslc-op/query/v3.0/os/oslc-query.html
3030

3131
IBM DOORS Next (DN), Engineering Test Manager (ETM), Engineering Workflow Manager (EWM) and Global Configuration Management (GCM) have supported OSLC query for many years now.
3232

elmclient/examples/ccm_simple_attachmentdownload.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@
1414
import elmclient.utils as utils
1515
import elmclient.rdfxml as rdfxml
1616

17+
# setup logging - see levels in utils.py
18+
#loglevel = "INFO,INFO"
19+
loglevel = "TRACE,OFF"
20+
levels = [utils.loglevels.get(l,-1) for l in loglevel.split(",",1)]
21+
if len(levels)<2:
22+
# assert console logging level OFF if not provided
23+
levels.append(None)
24+
if -1 in levels:
25+
raise Exception( f'Logging level {loglevel} not valid - should be comma-separated one or two values from DEBUG, INFO, WARNING, ERROR, CRITICAL, OFF' )
26+
utils.setup_logging( filelevel=levels[0], consolelevel=levels[1] )
27+
1728
logger = logging.getLogger(__name__)
1829

1930
jazzhost = 'https://jazz.ibm.com:9443'
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
##
2+
## © Copyright 2021- IBM Inc. All rights reserved
3+
# SPDX-License-Identifier: MIT
4+
##
5+
6+
# example of using the elmclient package to find a work item using OSLC Query for the id, then getting the RDF XML for it
7+
# use log2seq to get a sequence diagram of the interactions with EWM from the logs produced
8+
9+
import csv
10+
import logging
11+
12+
import lxml.etree as ET
13+
14+
import elmclient.server as elmserver
15+
import elmclient.utils as utils
16+
import elmclient.rdfxml as rdfxml
17+
18+
# setup logging - see levels in utils.py
19+
#loglevel = "INFO,INFO"
20+
loglevel = "TRACE,OFF"
21+
levels = [utils.loglevels.get(l,-1) for l in loglevel.split(",",1)]
22+
if len(levels)<2:
23+
# assert console logging level OFF if not provided
24+
levels.append(None)
25+
if -1 in levels:
26+
raise Exception( f'Logging level {loglevel} not valid - should be comma-separated one or two values from DEBUG, INFO, WARNING, ERROR, CRITICAL, OFF' )
27+
utils.setup_logging( filelevel=levels[0], consolelevel=levels[1] )
28+
29+
logger = logging.getLogger(__name__)
30+
31+
jazzhost = 'https://jazz.ibm.com:9443'
32+
username = 'ibm'
33+
password = 'ibm'
34+
35+
jtscontext = 'jts' # specifies /jts change to e.g. jts:jts23 if your jts is on a different context root such as /jts23
36+
ccmcontext = 'ccm' # specifies /ccm change to e.g. ccm:ccm2 if your ccm is on a different context root such as /ccm2
37+
38+
proj = "SGC Planning and Tasks"
39+
40+
workitemid=38
41+
42+
outfile = "ccm_simple_findworkitem_output.csv"
43+
44+
# caching control
45+
# 0=fully cached (but code below specifies queries aren't cached)
46+
# 1=clear cache initially then continue with cache enabled
47+
# 2=clear cache and disable caching
48+
caching = 0
49+
50+
# create our "server" which is how we connect to EWM
51+
# first enable the proxy so if a proxy is running it can monitor the communication with server (this is ignored if proxy isn't running)
52+
elmserver.setupproxy(jazzhost,proxyport=8888)
53+
theserver = elmserver.JazzTeamServer(jazzhost, username, password, verifysslcerts=False, jtsappstring=f"jts:{jtscontext}", appstring='rm', cachingcontrol=caching)
54+
55+
# create the CMM application interface
56+
ccmapp = theserver.find_app( f"ccm:{ccmcontext}", ok_to_create=True )
57+
58+
p = ccmapp.find_project( proj )
59+
60+
qcbase = p.get_query_capability_uri("oslc_cm1:ChangeRequest")
61+
62+
# query
63+
results = p.execute_oslc_query(
64+
qcbase,
65+
whereterms=[['dcterms:identifier','=',f'"{workitemid}"']],
66+
select=['*'],
67+
# prefixes={rdfxml.RDF_DEFAULT_PREFIX["dcterms"]:'dcterms'} # note this is reversed - url to prefix
68+
)
69+
70+
workitem_u = list(results.keys())[0]
71+
72+
print( f"Work item {workitemid} uri is {workitem_u}" )
73+
74+
# now retrieve it
75+
workitem_x = p.execute_get_xml( workitem_u, intent="Retrieve the workitem content" )
76+
77+
print( ET.tostring( workitem_x ) )
78+
79+
print( "Finished" )

elmclient/examples/dn_simple_createartifact.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# then you POST some basic content compliant with the shape to the factory URL
1717
# this must also specify the folder where you want the artifact to be created - which means you need to find the folder URL
1818
# folders are found using a OSLC Query capability for folders - this returns one level at a time
19-
# sowill likely need a series of ueries to find an existing folder
19+
# so will likely need a series of queries to find an existing folder
2020

2121
import logging
2222
import os.path

elmclient/examples/dn_simple_modulestructure.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
# example of accessing the module structure API https://jazz.net/wiki/bin/view/Main/DNGModuleAPI
77
# also see https://jazz.net/wiki/bin/view/Main/DNGModuleApiOverview
88
# prints the module content with indenting corresponding to headings and calculated section number
9-
# NOTE NOTE NOTE the section number calculation has not been fully verified/checked - it seems to work after superficial inspection
9+
# NOTE NOTE NOTE the section number calculation has not been fully verified/checked - it seems to work after superficial inspection but you probably should verify its correctness yourself
1010

11-
# provide on the commandline the id of an artifact in the same component and a new binding for it will be created in the structure - location hardcoded!
11+
# if you provide on the commandline the id of an artifact in the same component then a new binding for it will be created in the structure - location hardcoded!
1212

1313
import csv
1414
import logging
@@ -346,7 +346,7 @@ def iterator():
346346
raise Exception( "Root not at start of structure!" )
347347
# toinsert is already prepared
348348
# get the etag
349-
response,etag = c.execute_get_rdf_xml(
349+
response,etag = c.execute_get_json(
350350
structure_u,
351351
cacheable=False,
352352
headers={
@@ -394,6 +394,7 @@ def iterator():
394394

395395
# get the structure again afer the update
396396
modstructure_j = c.execute_get_json(structure_u, cacheable=False, headers={'vvc.configuration': config_u,'DoorsRP-Request-Type':'public 2.0', 'OSLC-Core-Version': None, 'Configuration-Context': None}, intent="Retrieve module structure (JSON) after update" ) # have to remove the OSLC-Core-Version and Configuration-Context headers, and provide vvc.configuration header
397+
print( "Structure updated - finishing now" )
397398
sys.exit(0)
398399
# scan all the entries into a dictionary keyed by the URL
399400
entries = {}

elmclient/examples/oslcquery.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def do_oslc_query(inputargs=None):
7070
# setup arghandler
7171
parser = argparse.ArgumentParser(description="Perform OSLC query on a Jazz application, with results output to CSV (and other) formats - use -h to get some basic help")
7272

73-
parser.add_argument('-f', '--searchterms', action='append', default=[], help='**APPS MAY NOT FULLY SUPPORT THIS** A word or phrase to search, returning ranked results"')
73+
parser.add_argument('-f', '--searchterms', action='append', default=[], help='**APPS MAY NOT FULLY SUPPORT THIS** A word or phrase to search, returning ranked results - use once for each term and oslcquery will insert the quotes and commas"')
7474
parser.add_argument('-n', '--null', action='append', default=[], help='Post-filter: A property that must be null (empty) for the resource to be included in the results - you can specify this option more than once')
7575
parser.add_argument('-o', '--orderby', default='', help='**APPS MAY NOT FULLY SUPPORT THIS** A comma-separated list of properties to sort by - prefix with "+" for ascending, "-" for descending- if -f/--searchterms is specified this orders items with the same oslc:score - to speciy a leading -, use = e.g. -o=-dcterms:title')
7676
parser.add_argument('-p', '--projectname', default=None, help='Name of the project - omit to run a query on the application')
@@ -80,6 +80,7 @@ def do_oslc_query(inputargs=None):
8080
parser.add_argument('-u', '--unique', action="store_true", help="Post-filter: Remove results with an rm_nav:parent value which are not-unique in the results on dcterms:identifier - this keeps module artifacts (which don't have rm_nav:parent) and artifacts for modules (which don't have a module artifact)) - RELEVANT ONLY FOR DOORS Next!")
8181
parser.add_argument('-v', '--value', action='append', default=[], help='Post-filter: A property name that must have a value for the resource to be included in the results - you can specify this option more than once')
8282
parser.add_argument('-A', '--appstrings', default=None, help=f'A comma-seperated list of apps, the query goes to the first entry, default "{APPSTRINGS}". Each entry must be a domain or domain:contextroot e.g. rm or rm:rm1 - Default can be set using environemnt variable QUERY_APPSTRINGS')
83+
parser.add_argument('-B', '--browser', default=None, help='Save results in HTML file and open in a browser')
8384
parser.add_argument('-C', '--component', help='The local component (optional, you *have* to specify the local configuration using -F)')
8485
parser.add_argument('-D', '--delaybetweenpages', type=float,default=0.0, help="Delay in seconds between each page of results - use this to reduce overall server load particularly for large result sets or when retrieving many properties")
8586
parser.add_argument('-E', '--globalproject', default=None, help="The global configuration project - optional if the globalconfiguration is unique in the gcm app")
@@ -187,6 +188,7 @@ def do_oslc_query(inputargs=None):
187188
serverport=80
188189
else:
189190
raise Exception( "Unknown scheme in jazzurl {args.jazzurl}" )
191+
190192
# now try to connect
191193
if not server.tcp_can_connect_to_url(serverhost, serverport, timeout=2.0):
192194
raise Exception( f"Server not contactable {args.jazzurl}" )
@@ -643,7 +645,7 @@ def safeint(s,nonereturns=0):
643645
print( f"Query result has {len(results.keys())} {resultsentries}" )
644646

645647
# COMPARE IS UNTESTED!
646-
if args.outputfile or args.compareresults:
648+
if args.outputfile or args.browser or args.compareresults:
647649
# write to CSV and/or compare with CSV
648650
# FIRST merge columns with same name - this merges types across components based on name
649651
# which is only need for queries in a GC. NOTE this doesn't attempt to use RDF URIs which it probably should :-o
@@ -682,13 +684,33 @@ def safeint(s,nonereturns=0):
682684
v[sk1] = otherexisting if otherexisting else existing
683685

684686
fieldnames = sorted(headings)
687+
685688
if args.outputfile:
689+
# produce a CSV file
686690
with open(args.outputfile, 'w', newline='', encoding='utf-8-sig') as csvfile:
687691
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, restval='')
688692
writer.writeheader()
689693
for k, v in results.items():
690694
writer.writerow(v)
691695

696+
if args.browser:
697+
# produce an html file and also open it in your browser
698+
with open(args.browser, 'w', newline='', encoding='utf-8-sig') as htmlfile:
699+
htmlfile.write( "<html><body><table><tr>")
700+
for fieldname in fieldnames:
701+
htmlfile.write( f"<th>{fieldname}</th>" )
702+
htmlfile.write( "</tr>" )
703+
for k, v in results.items():
704+
htmlfile.write( "<tr>" )
705+
for fieldname in fieldnames:
706+
htmlfile.write( f"<td>{v.get( fieldname,"" )}</td>" )
707+
htmlfile.write( "</tr>" )
708+
htmlfile.write( "</table>" )
709+
htmlfile.write( "</body></html>" )
710+
# display the report
711+
url = f'file://{os.path.abspath(args.browser)}'
712+
webbrowser.open(url, new=2) # open in new tab
713+
692714
if args.saveprocessedresults:
693715
open(args.saveprocessedresults+"_after.json","wt").write(json.dumps(results))
694716

@@ -762,7 +784,7 @@ def safeint(s,nonereturns=0):
762784
# print( f"creating {outputpath=}" )
763785
os.makedirs( outputpath, exist_ok=True)
764786

765-
# basically for RM: retrieve all the result resources (as RDF-XML) and store to one file per resource
787+
# intended for RM (not sure what it will do for the other apps): retrieve all the result resources (as RDF-XML) and store to one file per resource
766788
unknownid = 1
767789
retrieved = {}
768790
for k in results.keys():

elmclient/httpops.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,15 @@ def execute_delete(self, reluri, *, params=None, headers=None, **kwargs):
196196
response = request.execute( **kwargs )
197197
return response
198198

199-
def execute_get_json(self, reluri, *, params=None, headers=None, **kwargs):
199+
def execute_get_json(self, reluri, *, params=None, headers=None, return_etag = False, **kwargs):
200200
reqheaders = {'Accept': 'application/json'}
201201
if headers is not None:
202202
reqheaders.update(headers)
203203
request = self._get_get_request(reluri=reluri, params=params, headers=reqheaders)
204204
response = request.execute( **kwargs )
205205
result = json.loads(response.content)
206+
if return_etag:
207+
return (result,response.headers['ETag'])
206208
return result
207209

208210
def execute_get_binary( self, reluri, *, params=None, headers=None, **kwargs):

0 commit comments

Comments
 (0)