Skip to content

Commit 43d7598

Browse files
author
klehman-rally
committed
disqualifed a bunch of standard attributes of type COLLECTION when retrieving allowed values in SchemaItem.complate, modified allowedValues test for Story.Milestone, modified allowedValues.py example script, added zip of online docs into the build_dist.py script.
1 parent 2e74020 commit 43d7598

5 files changed

Lines changed: 118 additions & 69 deletions

File tree

build_dist.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ def main(args):
111111
store_packages('dist', [tarball])
112112
store_packages('dists', [tarball, zipped])
113113

114+
doc_dir = 'doc/build/html'
115+
doc_files = [path.split('/')[-1] for path in DOC_FILES if path.startswith(doc_dir)]
116+
webdocs_zip = make_online_docs_zipfile(PACKAGE_NAME, VERSION, doc_dir, doc_files)
117+
store_packages('dist', webdocs_zip)
118+
114119
################################################################################
115120

116121
def store_packages(subdir, files):
@@ -220,6 +225,25 @@ def make_tarball(pkg_name, pkg_version, base_files, example_files, doc_files):
220225

221226
################################################################################
222227

228+
def make_online_docs_zipfile(pkg_name, pkg_version, doc_dir, doc_files):
229+
zf_name = '%s-%s.docs.html.zip' % (pkg_name, pkg_version)
230+
cur_dir = os.getcwd()
231+
os.chdir(doc_dir)
232+
zf = zipfile.ZipFile(zf_name, 'w')
233+
for fn in doc_files:
234+
zf.write(fn, fn, zipfile.ZIP_DEFLATED)
235+
zf.close()
236+
237+
## The following is what has been done before on the command line, when you
238+
## get the recursion opt on the above logic you can drop the os.system call
239+
os.system("zip %s -r %s" % (zf_name, " ".join(doc_files)))
240+
##
241+
242+
os.chdir(cur_dir)
243+
return zf_name
244+
245+
################################################################################
246+
223247
def make_zipfile(pkg_name, pkg_version, base_files, example_files, doc_files):
224248
base_dir = '%s-%s' % (pkg_name, pkg_version)
225249

examples/allowedValues.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def main(args):
4545

4646
#rally = Rally(server, user, password, apikey=apikey, workspace=workspace, project=project, server_ping=False)
4747
rally = Rally(server, user, password, apikey=apikey,server_ping=False)
48-
rally.enableLogging('rally.hist.avl') # name of file you want the logging to go to
48+
#rally.enableLogging('rally.hist.avl') # name of file you want the logging to go to
4949

5050
target = entity
5151
if entity in ['Story', 'User Story', 'UserStory']:
@@ -67,20 +67,22 @@ def main(args):
6767
print("Warnings: %s" % response.warnings)
6868
td = response.next()
6969

70-
for attribute in td.Attributes:
70+
std_attributes = sorted([attr for attr in td.Attributes if attr.ElementName[:2] != 'c_'], key=lambda x: x.ElementName)
71+
custom_attributes = sorted([attr for attr in td.Attributes if attr.ElementName[:2] == 'c_'], key=lambda x: x.ElementName)
72+
all_attributes = std_attributes + custom_attributes
73+
for attribute in all_attributes:
7174
attr_name = attribute.Name.replace(' ', '')
7275
if attributes and attr_name not in attributes:
7376
continue
7477

75-
if attribute.AttributeType not in ['STATE', 'RATING', 'STRING']:
78+
if attribute.AttributeType not in ['STATE', 'RATING', 'STRING', 'COLLECTION']:
7679
continue
7780

7881
allowed_values = rally.getAllowedValues(target, attr_name)
79-
if not allowed_values:
80-
continue
81-
print(" %-28.28s (%s)" % (attr_name, attribute.AttributeType))
82-
for av in allowed_values:
83-
print(" |%s|" % av)
82+
if allowed_values:
83+
print(" %-28.28s (%s)" % (attr_name, attribute.AttributeType))
84+
for av in allowed_values:
85+
print(" |%s|" % av)
8486

8587
#################################################################################################
8688
#################################################################################################

pyral/entity.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ def __setattr__(self, item, value):
618618

619619
for entity_name, entity_class in list(classFor.items()):
620620
_rally_entity_cache[entity_name] = entity_name
621-
entity_class = None # reset...
621+
entity_class = None # reset...
622622

623623
# now stuff whatever other classes we've defined in this module that aren't already in
624624
# _rally_entity_cache
@@ -678,22 +678,25 @@ def complete(self, context, getCollection):
678678
There are some standard attributes whose type is COLLECTION that are not to be
679679
treated as "allowedValue" eligible; for the reason that the collections may be
680680
arbitrarily large and more frequently updated as opposed to more "normal"
681-
attributes like 'State', 'Severity', etc.
681+
attributes like 'State', 'Severity', etc., AND in many cases the values are
682+
on a per specific artifact/entity basis rather than values eligible for the
683+
artifact/entity on a workspace-wide basis.
682684
Sequence through each Attribute and call resolveAllowedValues for each Attribute.
683685
"""
686+
NON_ELIGIBLE_ALLOWED_VALUES_ATTRIBUTES = \
687+
[ 'Artifacts', 'Attachments', 'Changesets', 'Children', 'Collaborators',
688+
'Defects', 'DefectSuites', 'Discussion', 'Duplicates', 'Milestone',
689+
'Iteration', 'Release', 'Project',
690+
'Owner', 'SubmittedBy', 'Predecessors', 'Successors',
691+
'Tasks', 'TestCases', 'TestSets', 'Results', 'Steps', 'Tags',
692+
]
693+
684694
if self.completed:
685695
return True
686696
for attribute in sorted([attr for attr in self.Attributes if attr.AttributeType in ['RATING', 'STATE', 'COLLECTION']]):
687697
# only an attribute whose AttributeType is RATING or STATE will have allowedValues
688-
if attribute.ElementName in [ 'Attachments', 'Changesets', 'Children', 'Collaborators',
689-
'Defects', 'DefectSuites', 'Discussion', 'Duplicates',
690-
'Release', 'Iteration', 'Milestones',
691-
'Owner', 'Predecessors', 'Project', 'SubmittedBy',
692-
'Successors', 'Tasks', 'TestCases', 'TestSets', 'Results',
693-
'Steps',
694-
'Tags',
695-
]:
696-
continue
698+
if attribute.ElementName in NON_ELIGIBLE_ALLOWED_VALUES_ATTRIBUTES:
699+
continue
697700
attribute.resolveAllowedValues(context, getCollection)
698701

699702
self.completed = True
@@ -760,8 +763,8 @@ def __init__(self, attr_info):
760763
self.ElementName = str(attr_info['ElementName'])
761764
self.Name = str(attr_info['Name'])
762765
self.AttributeType = str(attr_info['AttributeType'])
763-
self.Subscription = attr_info.get('Subscription', None) # not having 'Subscription' should be rare
764-
self.Workspace = attr_info.get('Workspace', None) # apparently only custom fields will have a 'Workspace' value
766+
self.Subscription = attr_info.get('Subscription', None) # not having 'Subscription' should be rare
767+
self.Workspace = attr_info.get('Workspace', None) # apparently only custom fields will have a 'Workspace' value
765768
self.Custom = attr_info['Custom']
766769
self.Required = attr_info['Required']
767770
self.ReadOnly = attr_info['ReadOnly']

test/rally_targets.py

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,58 @@
11

2-
AGICEN = "liart.rallydev.com"
3-
PROD = "officialness.rallydev.com"
4-
5-
AGICEN_USER = "usernumbernonei@acme.com"
6-
AGICEN_PSWD = "B1G^S3Kretz"
7-
AGICEN_NICKNAME = "Wiley"
8-
API_KEY = "_ABC123DEF456GHI789JKL012MNO345PQR678STU901VWXZ"
9-
10-
DEFAULT_WORKSPACE = "Your Default Workspace"
11-
DEFAULT_PROJECT = "Your Default Project"
12-
NON_DEFAULT_PROJECT = "A non-default Project"
13-
ALTERNATE_WORKSPACE = "An Alternate Workspace"
14-
#ALTERNATE_PROJECT = "Dynamic"
15-
ALTERNATE_PROJECT = "An Alternate Project"
16-
17-
BOONDOCKS_WORKSPACE = 'Darwin Social Club'
18-
BOONDOCKS_PROJECT = 'Linoleum Blast'
19-
20-
PROJECT_SCOPING_TREE = {
21-
'TOP_LEVEL_PROJECT' : {'Arctic Elevation' :
22-
[ 'My ship is stuck and I cant get out!',
2+
AGICEN = "rally1.rallydev.com"
3+
PROD = "rally1.rallydev.com"
4+
5+
AGICEN_USER = "yodel@rallydev.com"
6+
AGICEN_PSWD = "Vistabahn"
7+
AGICEN_NICKNAME = "Yodel"
8+
API_KEY = "_x6CZhqQgiist6kTtwthsAAKHtjWE7ivqimQdpP3T4"
9+
10+
YETI_USER = "yeti@rallydev.com"
11+
YETI_PSWD = "Vistabahn"
12+
YETI_NAME = "Yeti"
13+
14+
DEFAULT_WORKSPACE = "AC WSAPI Toolkit Python"
15+
DEFAULT_PROJECT = "Sample Project"
16+
17+
NON_DEFAULT_PROJECT = "My Project"
18+
19+
ALTERNATE_WORKSPACE = "Kip's Playground"
20+
ALTERNATE_PROJECT = "Argonaut"
21+
22+
BOONDOCKS_WORKSPACE = 'Alligators BLD Unigrations'
23+
BOONDOCKS_PROJECT = 'Sandbox'
24+
25+
LARGE_WORKSPACE = 'Rally'
26+
LARGE_PROJECT_TREE_BASE = 'Rally'
27+
28+
PROD_USER = "kip@closedprojects.com"
29+
PROD_PSWD = "TwinLakes55"
30+
31+
ACCOUNT_WITH_NO_DEFAULTS_CREDENTIALS = ('nick@denver.com', 'P@$$w0rd')
32+
33+
PROJECT_SCOPING_TREE = {
34+
'TOP_LEVEL_PROJECT' : { 'Arctic Elevation' :
35+
['My ship is stuck and I cant get out',
2336
'Lurking under the water is 300 megatons of payback',
2437
'Narwhal beaks carry the flag of the GWN tribe',
25-
'Fear the winter, play in the summer on the floes'
26-
]
38+
'Fear the winter, play in the summer on the floes',
39+
]
2740
},
28-
'SUB_PROJECTS' : { 'Aurora Borealis' :
29-
[ 'Wavy Green Noodles', 'Astral Shock Treatment' ],
30-
'Sub Arctic Conditions' :
31-
[ 'Look at the salmon run',
41+
'SUB_PROJECTS' : { 'Aurora Borealis' :
42+
['Wavy Green Noodles', 'Astral Shock Treatment'],
43+
'Sub Arctic Conditions' :
44+
['Look at the salmon run',
3245
'Provide bears with polaroid medicine',
33-
'The wolf boy needs to howl at the moon',
34-
]
35-
},
36-
'BOTTOM_PROJECT' : { 'Bristol Bay Barons' :
37-
['Sealskin lap belt grinder', 'Shiny metal pieces']
38-
}
46+
'The wolf boy needs to howl at the moon'
47+
],
48+
},
49+
'BOTTOM_PROJECT' : { 'Bristol Bay Barons' :
50+
['Sealskin lap belt grinder', 'Shiny metal pieces']
51+
},
3952
}
4053

41-
ACCOUNT_WITH_NO_DEFAULTS_CREDENTIALS = ('mortel.e.woonded@torso00bam.com', 'T2Y*&9mm409')
42-
43-
4454
HTTPS_PROXY = "127.0.0.1:3128"
4555

4656
__all__ = [AGICEN, AGICEN_USER, AGICEN_PSWD, AGICEN_NICKNAME, PROD,
47-
DEFAULT_WORKSPACE, DEFAULT_PROJECT,
48-
NON_DEFAULT_PROJECT, ALTERNATE_WORKSPACE, ALTERNATE_PROJECT,
49-
PROJECT_SCOPING_TREE, HTTPS_PROXY]
57+
DEFAULT_WORKSPACE, DEFAULT_PROJECT,
58+
NON_DEFAULT_PROJECT, ALTERNATE_WORKSPACE, ALTERNATE_PROJECT, HTTPS_PROXY]

test/test_allowed_values.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313

1414
from rally_targets import AGICEN, AGICEN_USER, AGICEN_PSWD
1515
from rally_targets import API_KEY
16-
17-
EXAMPLE_ATTACHMENT_CONTENT = "The quck brown fox eluded the lumbering sloth\n"
16+
from rally_targets import LARGE_WORKSPACE, LARGE_PROJECT_TREE_BASE
1817

1918
##################################################################################################
2019

@@ -27,25 +26,37 @@ def test_getAllowedValues_query():
2726
avs = rally.getAllowedValues('Defect', 'State')
2827
assert len(avs) > 0
2928
assert len(avs) >= 4
30-
assert 'Open' in avs
29+
assert 'Open' in avs
3130
assert 'Closed' in avs
3231

3332
avs = rally.getAllowedValues('Defect', 'PrimaryColor')
3433
assert len(avs) > 0
3534
assert len(avs) >= 6 and len(avs) <= 8
36-
assert 'Red' in avs
35+
assert 'Red' in avs
3736
assert 'Magenta' in avs
3837

3938

40-
def test_getAllowedValues_for_UserStory():
39+
def test_getAllowedValues_for_UserStory_Milestone():
4140
"""
4241
Using a known valid Rally server and known valid access credentials,
4342
request allowed value information for the Milestones field of the UserStory entity.
43+
The Milestones field is a standard field of schema type COLLECTION whose value
44+
is entirely context dependent on the specific artifact so getting allowed values
45+
doesn't really make sense in the same way as an attribute like State or Severity
46+
that has a finite set of possible values that are the same for every Story or Defect
47+
in the workspace. Because of that characteristic, we return no allowed values for
48+
the Milestones attribute on the Story type. There are numerous other standard attributes
49+
with the same sort of semantic that are excluded from chasing the COLLECTION url and
50+
returning some list of values. (like, Changesets, Discussions, Tags, etc.)
4451
"""
45-
rally = Rally(server=AGICEN, user=AGICEN_USER, password=AGICEN_PSWD)
46-
avs = rally.getAllowedValues('Story', 'Milestones')
47-
assert len(avs) == 1
48-
assert avs == [True]
52+
rally = Rally(server=AGICEN, apikey=API_KEY)
53+
response = rally.get('Milestone', fetch=True, workspace=LARGE_WORKSPACE,
54+
project=LARGE_PROJECT_TREE_BASE, projectScopeDown=True)
55+
milestones = [item for item in response]
56+
assert len(milestones) > 150
57+
58+
avs = rally.getAllowedValues('Story', 'Milestone')
59+
assert avs is None
4960

5061

5162
def test_getAllowedValues_for_custom_collections_field_Defect():

0 commit comments

Comments
 (0)