https://dev.arvados.org/https://dev.arvados.org/favicon.ico?15576888422017-08-15T19:11:45ZArvadosArvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=541232017-08-15T19:11:45ZTom Morristfmorris@veritasgenetics.com
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/54123/diff?detail_id=52079">diff</a>)</li><li><strong>Target version</strong> changed from <i>Arvados Future Sprints</i> to <i>2017-08-30 Sprint</i></li><li><strong>Story points</strong> set to <i>2.0</i></li></ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=542122017-08-16T19:32:17ZTom Morristfmorris@veritasgenetics.com
<ul><li><strong>Target version</strong> changed from <i>2017-08-30 Sprint</i> to <i>2017-09-13 Sprint</i></li></ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=546842017-08-30T19:26:02ZTom Morristfmorris@veritasgenetics.com
<ul><li><strong>Target version</strong> changed from <i>2017-09-13 Sprint</i> to <i>2017-09-27 Sprint</i></li></ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=550322017-09-14T20:41:05ZLucas Di Pentimalucas.dipentima@curii.com
<ul><li><strong>Assigned To</strong> set to <i>Lucas Di Pentima</i></li></ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=553842017-09-27T04:05:03ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Updates at <a class="changeset" title="12018: Avoid making duplicate api calls to add an already added user when the input file has the ..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/fd14dc21b4dc52b3168f32a644a4167cc55ab919">fd14dc21b</a> - branch <code>12018-sync-groups-tool</code> (WIP)</p>
<p>Added a first version of the new command <code>arv-sync-groups</code>, it currently does most of the described requirements, it's pending the reporting of untagged existing groups, and created group uuids.</p>
<p>About the first missing feature, I have a doubt: Should these groups be created as "system groups"? (ie: owner_uuid == {prefix}-tpzed-0000000000000). I ask because there's a unique index on groups that's composed of (owner_uuid, group_name) so to check for untagged but existing groups, I suppose I have to retrieve all groups from a particular owner and avoid name collision with other user's groups.</p>
<p>Related to the above, here's another question: Should this command use credentials from the environment vars as <code>arv-put</code> and others, or as it's more an admin tool, should read auth credentials from some config file?</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=553852017-09-27T04:05:19ZLucas Di Pentimalucas.dipentima@curii.com
<ul><li><strong>Status</strong> changed from <i>New</i> to <i>In Progress</i></li></ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=553882017-09-27T13:27:03ZTom Cleggtom@curii.com
<ul></ul>How about making a single parent group, and using that as the owner_uuid of all synchronized groups?
<ul>
<li>If --parent-group-uuid arg is given, use that uuid as the parent. Log a warning if it's not owned by system_user.</li>
<li>Otherwise, create/find a group (owned by system-user) named "Externally synchronized groups", and use that as the parent</li>
</ul>
<p>For credentials, just let the SDK do its default thing (env vars, then ~/.config/arvados/settings.conf).</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=554602017-09-27T18:21:15ZLucas Di Pentimalucas.dipentima@curii.com
<ul><li><strong>Target version</strong> changed from <i>2017-09-27 Sprint</i> to <i>2017-10-11 Sprint</i></li></ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=559112017-10-11T18:52:55ZLucas Di Pentimalucas.dipentima@curii.com
<ul><li><strong>Target version</strong> changed from <i>2017-10-11 Sprint</i> to <i>2017-10-25 Sprint</i></li></ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=559732017-10-12T17:17:10ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>First golang version at <a class="changeset" title="12018: Remote group synchronization tool. First draft. Arvados-DCO-1.1-Signed-off-by: Lucas Di P..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/46141b6c9098f30dcd6644845887789c1c9006da">46141b6c9098f30dcd6644845887789c1c9006da</a></p>
<p>It's basically a direct translation of the python version, I feel that I used too many type assertions, making the code "ugly", difficult to read. I suppose I should be creating specific types, for example a Set type instead of using <code>map[string]struct{}</code></p>
<p>I was thinking about adding <code>ListAll()</code> to the go SDK, as it exist on PySDK <code>arvados.util</code> package. Do you think it would be useful?</p>
<p>I would love some feedback on golang code styling, any tips will be welcome. Thanks!</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=559872017-10-12T21:13:54ZTom Cleggtom@curii.com
<ul></ul><p>You're right about all the type shenanigans. That map[string]arvadosclient.Dict stuff in the arvadosclient module wasn't the best idea. We can do something more like <a class="source" href="https://dev.arvados.org/projects/arvados/repository/arvados/entry/services/keep-balance/collection.go">source:services/keep-balance/collection.go</a> now -- we'll have to add Group and GroupList types in sdk/go/arvados (<a class="source" href="https://dev.arvados.org/projects/arvados/repository/arvados/entry/sdk/go/arvados/log.go">source:sdk/go/arvados/log.go</a> is probably a good one to copy & modify) but once that's done, you can just load json right into a struct and use its fields like real fields, instead of all the string-keys and casting stuff.</p>
<p>SDK aside, you definitely shouldn't use arvadosclient.Dict for things like remoteGroups. Make a struct type with the fields you need to track, maybe something like <pre>
type groupInfo struct {
Group arvados.Group
PreviousMembers map[string]bool
CurrentMembers map[string]bool
}
</pre></p>
<p>You can say fmt.Sprintf("%s", err), you don't need to say err.Error()</p>
<p>Golang style says error strings should start lowercase -- otherwise you end up chaining together into "Can't do this: Trouble at the mill: Shot off, completely."</p>
<p>I wouldn't bother trying to os.Stat() the input file to predict whether opening will work. Just open it, and if that doesn't work, report the error.</p>
<p>Instead of ("error reading csv file: %s", err) ... how about ("error reading %q: %s", *srcPath, err)</p>
<p>Appending to (and iterating over) a nil slice works, so you can probably drop some of the stuff like make([]interface{}, 0) -- just "var links []interface{}" would work here.</p>
<p>The "contains" function is unnecessary. You can do this in subtract():</p>
<pre>
if _, found := setB[element]; !found {
result[element] = struct{}{}
}
</pre>
<p>Another way would be to use a map[string]bool instead of a map[string]struct{}, always assign true when you're assigning, and do this</p>
<pre>
if !setB[element] {
result[element] = struct{}{}
}
</pre> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=561022017-10-18T17:29:28ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Updates at <a class="changeset" title="12018: Several changes, listed below: * Removed initial Python version. * Removed superfluous inp..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/ed6af9cb44515fe1759eeebabfcac4a068fd697c">ed6af9cb4</a></p>
<p>This commit is about tidying up the code, following the suggestions on note-11:</p>
<ul>
<li>Removed initial Python version.</li>
<li>Removed superfluous input files checks and moved file opening to be before any API calls, to avoid doing them is there's a problem with the file.</li>
<li>Added user, group & link types so they're populated by json decoding.</li>
<li>Cleaned up ListAll() func so it can work with different resource types.</li>
<li>Changed usage of set style types from map[string]struct{} to be map[string]bool to simplify membership checking.</li>
<li>Corrected error messages to start with lowercase.</li>
<li>Added more debug messages for -verbose mode.</li>
</ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=561122017-10-19T13:10:21ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Updates at <a class="changeset" title="12018: Skip CSV register if one of its fields is empty Arvados-DCO-1.1-Signed-off-by: Lucas Di P..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/ea10340803abade2d35212866fcbc1beb1acd533">ea10340803abade2d35212866fcbc1beb1acd533</a></p>
<ul>
<li>Added <code>-parent-group-uuid</code> parameter to specify a parent group for the remote groups. This group should be owned by the system user. If not provided, the tool will search for a group named "Externally synchronized groups" owned by the system user, or create one if not found.</li>
<li>Skip (with a warning message) CSV lines with an empty field</li>
<li>Check if current user is admin, fail otherwise.</li>
<li>Use the parent group uuid when searching/creating remote groups.</li>
</ul>
<p>Tests are still pending.</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=561192017-10-19T14:59:43ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Found a bug, working on it.</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=561432017-10-19T20:24:38ZTom Cleggtom@curii.com
<ul></ul><p>Functional</p>
<p>In the "evict" code, it seems like we should be removing user→group <em>and</em> group→user links, and we shouldn't be filtering on name=can_read</p>
<p>Arvados stuff</p>
<p>Should use arvados.ResourceListParams instead of arvadosclient.Dict for limit, offset, etc.</p>
<p>Should use arvados.User instead of a custom user type -- just need to add the Email field to <a class="source" href="https://dev.arvados.org/projects/arvados/repository/arvados/entry/sdk/go/arvados/user.go">source:sdk/go/arvados/user.go</a>, and a UserList type similar to CollectionList in <a class="source" href="https://dev.arvados.org/projects/arvados/repository/arvados/entry/sdk/go/arvados/collection.go">source:sdk/go/arvados/collection.go</a></p>
<p>I suspect the only resourceList interface you really need to implement ListAll is "Len() int"</p>
<p>In ListAll, simplify by always using limit=2^31-1 and terminate when len(response.Items)==0</p>
<p>Paging code should use order=uuid instead of the default modified_at -- less likely to miss groups in races that way</p>
<p>Shouldn't we be using group properties instead of tag links for "tagged as remote"? We can't yet filter on properties using the API filters, but the expected usage is for all role groups to be synchronized, so it might be best to filter on group_class==null and do additional filtering by properties on the client side for now.</p>
<p>Go style</p>
<p>Don't split lines just to stay in 80 chars (e.g., "error creating tag for group %q: %s")</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=561992017-10-24T12:15:30ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Updates at <a class="changeset" title="12018: User, UserList, Group & GroupList types go into Go SDK. Arvados-DCO-1.1-Signed-off-by: Lu..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/a0dcf5e4b837826ff75b9785bd3f3e695ac7bdca">a0dcf5e4b</a></p>
<ul>
<li>Manage two way links for group memberships.</li>
<li>Simplify ListAll()</li>
<li><code>resourceList</code> interface simplification.</li>
<li>Stop using <code>arvadosclient</code> in favor of <code>arvados</code> package for API server access.</li>
<li>Do not tag remote groups with tag links. Use the parent group as a filter to search for remote groups.</li>
<li>On group members loading, check that both membership links exist, just in case the command was interrupted after creating the first link for a user.</li>
<li><code>User</code>, <code>UserList</code>, <code>Group</code> & <code>GroupList</code> types go into Go SDK. (<code>User</code> type already existed, was expanded)</li>
</ul>
Pending:
<ul>
<li>Tests</li>
</ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=562012017-10-24T12:18:05ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>To be able to manually try this, I've symlinked my <code>sdk/go/arvados</code> dev copy to <code>~/go/src/git.curoverse.com/arvados.git/sdk/go/arvados</code></p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=562172017-10-24T19:12:27ZPeter Amstutzpeter.amstutz@curii.com
<ul></ul><p>As a general comment, doMain() is 340 lines long, which makes it a bit hard to follow. It would be helpful if it was pulled apart into 3-4 smaller subroutines.</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=562192017-10-25T01:56:26ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>I've broken down the code into several funcs - <a class="changeset" title="12018: Move remote group retrieving and member removal code to separate functions. Arvados-DCO-1..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/e2c27eaae38a904a4b05d800affdc7860ee24e79">e2c27eaae38a904a4b05d800affdc7860ee24e79</a><br />Continuing with the tests.</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=562212017-10-25T14:29:59ZPeter Amstutzpeter.amstutz@curii.com
<ul></ul><ul>
<li>The main loop that reads the CSV file should also go in its own function.</li>
</ul>
<ul>
<li>The permission is "can_manage" not just "manage"</li>
</ul>
<p>However I don't think should be granting "can_manage", because that means any member of the group can add and remove other members, which doesn't make sense since it is externally managed. It should be "can_write", which will allow writes to projects which are shared with the group for writing.</p>
<p>However, I think it would be even better to have a 3rd column that specifies "read" or "write" and update permission link accordingly. (The code will need to take into account that a user could transition between read and write permission on a given group).</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=562272017-10-25T15:25:08ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Updates at <a class="changeset" title="12018: CSV file processing code separated to its own function. Fixed members permission link, rep..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/f38cea6fa1ac48dd50789d9e2ec653b6d961c461">f38cea6fa</a></p>
<ul>
<li>Replaced "manage" with "can_write" permission links</li>
<li>Separated CSV file processing code into its own function</li>
<li>Additional code splitting to be able to test it easily</li>
<li>First couple of basic tests in place</li>
</ul>
<p>Pending: write tests mocking arvados client calls</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=562592017-10-25T18:46:27ZLucas Di Pentimalucas.dipentima@curii.com
<ul><li><strong>Target version</strong> changed from <i>2017-10-25 Sprint</i> to <i>2017-11-08 Sprint</i></li></ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=563512017-10-30T14:40:16ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Updates at <a class="changeset" title="12018: Fixed positional argument handling. Fixed & added tests. Arvados-DCO-1.1-Signed-off-by: L..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/7327a28eb1d90b161708e9e4e855cf80f41f20ae">7327a28eb</a></p>
<ul>
<li>Membership removal fix</li>
<li>Changed <code>-path <input-file></code> to be a positional argument</li>
<li>Enhanced help message</li>
<li>Fixed group creation by adding them as <code>role</code> group class (they now appear on the Sharing tab)</li>
<li>Added several tests</li>
</ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=563642017-10-30T19:00:25ZPeter Amstutzpeter.amstutz@curii.com
<ul></ul><ul>
<li>The "Link" struct should be added to the "arvados" package.</li>
</ul>
<p>The signature to ProcessFile() is 247 characters long. It should be broken up over multiple lines for readability.</p>
<p>I deliberately gave it a broken CSV file:</p>
<pre>
external_group,a@a
external_group,b@b
"another group,b@b
</pre>
<p>The error is very unhelpful:</p>
<pre>
2017/10/30 18:38:27 Group sync starting. Using "email" as users id and parent group UUID "<a href="https://arvadosapi.com/2tlax-j7d0g-djxpyba3fu6hug4">2tlax-j7d0g-djxpyba3fu6hug4</a>"
2017/10/30 18:38:27 Found 4 users
2017/10/30 18:38:27 Found 3 remote groups
2017/10/30 18:38:27 error reading "blah.csv": %!s(<nil>)
</pre>
<p>Perhaps it could keep a count of lines so at least it can say "parse error on line X".</p>
It looks like it could attempt to create multiple instances of the group→user can_read Link record, if there was an error between the two CreateLink calls. You should either
<ul>
<li>refactor so that doesn't happen (record the existence of "group→user" and "group→user" links separately instead of a single "membership flag)</li>
<li>or confirm that creating multiple links is harmless (I think this is true)</li>
</ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=563892017-10-30T22:50:04ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Updates at <a class="changeset" title="12018: Enhanced error message when having a parse error. Added test cleanup. Added test to confir..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/eb772d62ecc9d1ad61df7bbf08a572fda689c5be">eb772d62e</a></p>
<ul>
<li>Link & LinkList types moved to Go SDK</li>
<li>Enhanced error message when having a parsing error.</li>
<li>Added test tear down function that cleans up all the links & remote groups created by every test.</li>
<li>Added test that proves that records with empty fields are skipped</li>
</ul>
<p>Regarding possible duplicated links, as far as I understand, they're harmless and will be deleted when removing the membership.</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=563932017-10-31T02:19:20ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Updates at <a class="changeset" title="12018: Enhanced readability of ProcessFile() function. Added test using usernames instead of emai..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/9b83c3ee103d121e9406fa93e6e95ad303dee577">9b83c3ee1</a></p>
<ul>
<li>Enhanced readability of <code>ProcessFile()</code> function.</li>
<li>Added test using usernames instead of emails as users indentifiers.</li>
</ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=564552017-10-31T20:00:36ZPeter Amstutzpeter.amstutz@curii.com
<ul></ul><p>For the tests, you should call arvadostest.StartAPI() instead of assuming the environment.</p>
<p>Instead of adding a bunch of test code to remove groups, consider using the database reset endpoint (this resets the database contents to fixtures). From run_test_server.py:</p>
<pre>
httpclient.request(
'https://{}/database/reset'.format(existing_api_host),
'POST',
headers={'Authorization': 'OAuth2 {}'.format(token)})
</pre> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=564582017-10-31T21:11:18ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Updates at:8fc7e0dd5</p>
<ul>
<li>Added API server start/stop calls to test suite.</li>
<li>Removed manual database cleanup in favour of using the database reset endpoint after every test run.</li>
<li>Moved tool config set up code from the suite set up call to the test set up, because the database get reset to fixture state after every test run.</li>
</ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=565182017-11-02T14:42:17ZPeter Amstutzpeter.amstutz@curii.com
<ul></ul><p>This LGTM @ <a class="changeset" title="12018: Added API server start/stop calls to test suite. Removed manual database cleanup in favour..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/8fc7e0dd5d214e3881b8a56669f82d76aa70bfdb">8fc7e0dd5d214e3881b8a56669f82d76aa70bfdb</a></p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=565232017-11-02T15:05:05ZAnonymous
<ul><li><strong>Status</strong> changed from <i>In Progress</i> to <i>Resolved</i></li></ul><p>Applied in changeset arvados|commit:cc6f86f15e0d187cc1c84b874be3d2da7b20d19f.</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=571712017-11-22T20:47:47ZTom Morristfmorris@veritasgenetics.com
<ul></ul><p>Where is this tool packaged? Documented?</p>
<p>As an aside, it's too late now, but when did we switch from using Python for our client command line tools? I thought we were trying to reduce language diversity in the CLI tools. If we're going to make major changes like this, we should have an explicit decision about them.</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=572212017-11-23T21:02:31ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Added documentation at <a class="changeset" title="12018: Added documentation for arv-sync-groups Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/4b16a16d0de04504f0172ce391da1fa603eb653c">4b16a16d0</a> - branch <code>12018-tool-docs</code></p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=572282017-11-24T16:58:05ZLucas Di Pentimalucas.dipentima@curii.com
<ul><li><strong>Status</strong> changed from <i>Resolved</i> to <i>In Progress</i></li><li><strong>Target version</strong> changed from <i>2017-11-08 Sprint</i> to <i>2017-12-06 Sprint</i></li></ul> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=572292017-11-24T17:25:43ZLucas Di Pentimalucas.dipentima@curii.com
<ul></ul><p>Updates at <a class="changeset" title="12018: Replaced '<' & '>' with their html entities counterparts Arvados-DCO-1.1-Signed-off-by: L..." href="https://dev.arvados.org/projects/arvados/repository/arvados/revisions/53a6cafcdc13c0b8ed60f1a2912d660f2e5b415d">53a6cafcd</a></p>
<p>HTML entities fixes</p> Arvados - Feature #12018: Synchronize group membership with external data sourcehttps://dev.arvados.org/issues/12018?journal_id=578932017-12-04T20:08:56ZLucas Di Pentimalucas.dipentima@curii.com
<ul><li><strong>Status</strong> changed from <i>In Progress</i> to <i>Resolved</i></li></ul>