Modify

Opened 11 years ago

Closed 10 years ago

Last modified 6 years ago

#11340 closed enhancement (worksforme)

Confusion with timestamp - lack of documentation

Reported by: kubes@… Owned by: osimons
Priority: normal Component: XmlRpcPlugin
Severity: normal Keywords: documentation
Cc: Trac Release: 1.0

Description (last modified by osimons)

I would like to start using Trac tickets, but first I need to synchronize thousands of ticket with trac via XmlRpcPlugin.

I cannot find any documentation with properties and their types to provide to ticket_create and ticket_update. Documentation http://www.jspwiki.org/Wiki.jsp?page=WikiRPCInterface2 linked from http://trac-hacks.org/wiki/XmlRpcPlugin doesn't work.

Can you please provide more complex example how to use timestamps when calling ticket_update? I cannot get it work. I would expect that I should provide timestamp of type int32 in _ts field? But when I call _trac.ticket_get(id) I obtain very long number ["_ts"] = "1380897120346000" which seem not be be int32 unix timestamp.

I probably use wrong type beause of "Server returned a fault exception: [1] 'unsupported type for timedelta microseconds component: unicode' while executing 'ticket.update()'". My code C# code is:

  public XmlRpcStruct GetProperties()
        {           
            XmlRpcStruct dict = new XmlRpcStruct();            
            dict.Add("type", Type.ToString());
            dict.Add("time", Time.ToString("yyyyMMddTHHmmsszzz"));
            dict.Add("changetime", Changetime.ToString("yyyyMMddTHHmmsszzz"));            
            dict.Add("_ts", DateTimeToUnixTimeStamp(DateTime.Now));
            dict.Add("component", Component ?? string.Empty);
            dict.Add("severity", Severity.ToString());
            dict.Add("priority", Priority.ToString());
            dict.Add("owner", Owner ?? string.Empty);
            dict.Add("reporter", Reporter ?? string.Empty);
            dict.Add("cc", Cc ?? string.Empty);
            dict.Add("version", Version ?? string.Empty);
            dict.Add("milestone", Milestone ?? string.Empty);
            dict.Add("status", Status.ToString());
            dict.Add("resolution", Resolution.ToString());
            dict.Add("summary", Summary ?? string.Empty);
            dict.Add("description", Description?? string.Empty);
            dict.Add("keywords", Keywords ?? string.Empty);                   
            return dict;
        }

        public static int DateTimeToUnixTimeStamp(DateTime dateTime)
        {         
            System.DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return (int)((dateTime - epoch.ToLocalTime()).TotalSeconds);
        }

Attachments (0)

Change History (10)

comment:1 Changed 11 years ago by osimons

Description: modified (diff)

When updating a ticket, all you have to do is pass whatever _ts value you get from the server. The timestamp is a marker for the server to make sure that updates are performed based on latest values, and not on stale values. So, in order to do ticket.update you first have to do ticket.get.

What this value is exactly has changed with Trac. In earlier Trac versions (pre-0.12) timestamped based on seconds since epoch. However, with 0.12 this changed so that Trac now timestamps based on microseconds. So dividing by 1.000.000 will get you the seconds since epoch. The plugin works with both versions.

However, all this is implementation details you should not be concerned with. _ts is really just a token used to ensure valid updates.

As for available fields, that also depends on your server. If you have defined custom fields they will also appear as available fields for creation and updating. All fields are returned by the ticket.getTicketFields call. However, as you noticed, the plugin does not know anything about types, expected inputs, validation rules or similar. You may even have validation/manipulator plugins installed the plugin cannot know about. The plugin takes the data as if entered through web, and makes Trac perform validation of input - applying changes if all is correct, or returning all the warnings if problems are found. With fields as if entered through web, all ticket attributes should be passed as plain strings.

If you have specific suggestions for more correct documentation, then by all means add patch or suggestions to this ticket.

comment:2 Changed 11 years ago by anonymous

I understand you point of view, that XmlRpcPlugin is just redirecting the call to trac. BUT trac has user interface and does not need end user documentation of methods and properties. If there is any documentation, how to call these internals, it would be OK, but I cannot found such documentation.

For me ,the end user, trac is blackbox and it is very hard for me to hunt for these properties and correct values and it is very easy form me to destroy Trac with invalid call through the plugin.

I have found waht was couse the issue and destroying trac database. I have comment out the followinf lines and get the _ts like object and send it again like a object (not int32):

 public XmlRpcStruct GetProperties()
        {           
            XmlRpcStruct dict = new XmlRpcStruct();            
            dict.Add("type", Type.ToString());
            //dict.Add("time", Time.ToString("yyyyMMddTHHmmsszzz"));*/
            //dict.Add("changetime", Changetime.ToString("yyyyMMddTHHmmsszzz"));    
            //dict.Add("_ts", DateTimeToUnixTimeStamp(DateTime.Now) + "000000");
            // http://trac-hacks.org/ticket/11340#comment:1    
            dict.Add("component", Component ?? string.Empty);
            dict.Add("severity", Severity.ToString());
            dict.Add("priority", Priority.ToString());
            dict.Add("owner", Owner ?? string.Empty);
            dict.Add("reporter", Reporter ?? string.Empty);
            dict.Add("cc", Cc ?? string.Empty);
            dict.Add("version", Version ?? string.Empty);
            dict.Add("milestone", Milestone ?? string.Empty);
            dict.Add("status", Status.ToString());
            dict.Add("resolution", Resolution.ToString());
            dict.Add("summary", Summary ?? string.Empty);
            dict.Add("description", Description?? string.Empty);
            dict.Add("keywords", Keywords ?? string.Empty);                   
            return dict;
        }

        private void KeepTryingUpdatingTicket(int id, Ticket t)
        {
            for (int i = 0; true; i++)
            {
                try
                {
                    string action;
                    switch (t.Resolution)
                    {
                        case Resolution.@fixed:
                            action = "resolve";
                            break;
                        case Resolution.invalid:
                        case Resolution.wontfix:
                        case Resolution.duplicate:
                        case Resolution.reject:
                        case Resolution.worksforme:
                        default:
                            action = string.Empty;
                            break;
                    }

                    object[] oldTicket = _trac.ticket_get(id);
                    //I don't concern with collision, it is first import, last is winner
                    XmlRpcStruct oldStruct = (XmlRpcStruct)oldTicket[3];
                    XmlRpcStruct newStruct = t.GetProperties();
                    //keep timestamp to confirm that it is not collision
                    object ts = oldStruct["_ts"];
                    newStruct["_ts"] = ts;

                    _trac.ticket_update(id, action, newStruct);
                    Console.WriteLine(t.Id + " updated");
                    return;
                }
                catch (WebException ex)//connection problem
                {
                    Console.WriteLine("problem: " + ex.Message);
                    Thread.Sleep(100 * i);
                    if (i == 10)//10 attempts
                        throw ex;
                }
            }
        }

Anyway I would like to put my own changetime and time. so what is the correct changetime format and time format?

comment:3 Changed 11 years ago by osimons

All right. I see you got the _ts sorted by just returning whatever you got served when getting the ticket.

I said all ticket fields in Trac are in essence text fields. By that i mean XML-RPC String type. However, that is not correct for time and changetime. These fields are XML-RPC DateTime objects. The same is true for the when parameter supported by some of the methods, but in that case the call signature mentions it explicitly so details are not hidden in a general attributes construct. Anyway for DateTime your library no doubt has a way of creating those objects from native types.

PS! The reason ticket fields are 'hidden' in attributes instead of just being just regular keyword arguments to the method, is that the number of fields may vary both between Trac versions and between installations. A project can have any number of custom fields, called anything the admin wishes. I can't support that in the method signature - although explicit signatures is otherwise what I prefer when possible.

The plugin contains various tests, and these tests are all functional tests meaning that a test run actually starts a server and calls methods and checks result against a running server - calling just as you would do (only using Python of course). If you have a look at source:/xmlrpcplugin/trunk/tracrpc/tests/ticket.py you may get some pointers.

comment:4 Changed 11 years ago by kubes@…

Finally I manage to write synchronization between our old tracking system and Trac using your plugin. The only thing I didn't manage to do is to put time and changetime property.

If I use XmlRpc.NET library and I use time as DateTime object, I got the same error as #10711 "'datetime.datetime' object has no attribute 'strip while executing 'ticket.create"

I understand the there is some method to trim the value and the datetime does not have trim method. It must be some issue connected with usage Trac + XmlRpc.NET. I have notice that in unittest xmlrpcplugin/trunk/tracrpc/tests/ticket.py on line 391 where do you use

dt = to_xmlrpc_datetime(to_datetime(None, utc))

so, it must be possible to send datetime to ticket create, which does not work for me. Is there some difference?

I use fiddler Http debugger to capture the XmlRpc request and response:

<?xml version="1.0"?>
<methodCall>
  <methodName>ticket.create</methodName>
  <params>
    <param>
      <value>
        <string>smazat</string>
      </value>
    </param>
    <param>
      <value>
        <string>smazat</string>
      </value>
    </param>
    <param>
      <value>
        <struct>
          <member>
            <name>type</name>
            <value>
              <string>defect</string>
            </value>
          </member>
          <member>
            <name>time</name>
            <value>
              <dateTime.iso8601>20131011T10:08:41</dateTime.iso8601>
            </value>
          </member>
          <member>
            <name>component</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>severity</name>
            <value>
              <string>highest</string>
            </value>
          </member>
          <member>
            <name>priority</name>
            <value>
              <string>blocker</string>
            </value>
          </member>
          <member>
            <name>owner</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>reporter</name>
            <value>
              <string>smazat</string>
            </value>
          </member>
          <member>
            <name>cc</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>version</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>milestone</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>status</name>
            <value>
              <string>new</string>
            </value>
          </member>
          <member>
            <name>resolution</name>
            <value>
              <string>invalid</string>
            </value>
          </member>
          <member>
            <name>summary</name>
            <value>
              <string>smazat</string>
            </value>
          </member>
          <member>
            <name>description</name>
            <value>
              <string>smazat</string>
            </value>
          </member>
          <member>
            <name>keywords</name>
            <value>
              <string />
            </value>
          </member>
        </struct>
      </value>
    </param>
  </params>
</methodCall>
<?xml version='1.0'?>
<methodResponse>
<fault>
<value><struct>
<member>
<name>faultCode</name>
<value><int>1</int></value>
</member>
<member>
<name>faultString</name>
<value><string>''datetime.datetime' object has no attribute 'strip'' while executing 'ticket.create()'</string></value>
</member>
</struct></value>
</fault>
</methodResponse>

Can you plese tell me what is wrong? What is different from the unittest?

comment:5 in reply to:  4 Changed 11 years ago by anonymous

Replying to kubes@…:

Finally I manage to write synchronization between our old tracking system and Trac using your plugin. The only thing I didn't manage to do is to put time and changetime property.

If I use XmlRpc.NET library and I use time as DateTime object, I got the same error as http://trac.edgewall.org/ticket/10711 "'datetime.datetime' object has no attribute 'strip while executing 'ticket.create"

I understand the there is some method to trim the value and the datetime does not have trim method. It must be some issue connected with usage Trac + XmlRpc.NET. I have notice that in unittest xmlrpcplugin/trunk/tracrpc/tests/ticket.py on line 391 where do you use

dt = to_xmlrpc_datetime(to_datetime(None, utc))

so, it must be possible to send datetime to ticket create, which does not work for me. Is there some difference?

I use fiddler Http debugger to capture the XmlRpc request and response:

<?xml version="1.0"?>
<methodCall>
  <methodName>ticket.create</methodName>
  <params>
    <param>
      <value>
        <string>smazat</string>
      </value>
    </param>
    <param>
      <value>
        <string>smazat</string>
      </value>
    </param>
    <param>
      <value>
        <struct>
          <member>
            <name>type</name>
            <value>
              <string>defect</string>
            </value>
          </member>
          <member>
            <name>time</name>
            <value>
              <dateTime.iso8601>20131011T10:08:41</dateTime.iso8601>
            </value>
          </member>
          <member>
            <name>component</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>severity</name>
            <value>
              <string>highest</string>
            </value>
          </member>
          <member>
            <name>priority</name>
            <value>
              <string>blocker</string>
            </value>
          </member>
          <member>
            <name>owner</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>reporter</name>
            <value>
              <string>smazat</string>
            </value>
          </member>
          <member>
            <name>cc</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>version</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>milestone</name>
            <value>
              <string />
            </value>
          </member>
          <member>
            <name>status</name>
            <value>
              <string>new</string>
            </value>
          </member>
          <member>
            <name>resolution</name>
            <value>
              <string>invalid</string>
            </value>
          </member>
          <member>
            <name>summary</name>
            <value>
              <string>smazat</string>
            </value>
          </member>
          <member>
            <name>description</name>
            <value>
              <string>smazat</string>
            </value>
          </member>
          <member>
            <name>keywords</name>
            <value>
              <string />
            </value>
          </member>
        </struct>
      </value>
    </param>
  </params>
</methodCall>
<?xml version='1.0'?>
<methodResponse>
<fault>
<value><struct>
<member>
<name>faultCode</name>
<value><int>1</int></value>
</member>
<member>
<name>faultString</name>
<value><string>''datetime.datetime' object has no attribute 'strip'' while executing 'ticket.create()'</string></value>
</member>
</struct></value>
</fault>
</methodResponse>

Can you plese tell me what is wrong? What is different from the unittest?

comment:6 Changed 11 years ago by osimons

Could you please also append the full error from trac.log? The traceback would be very useful for trying to understand your problem.

comment:7 Changed 11 years ago by kubes@…

Here is a part of the trac.log:

2013-10-11 10:57:55,098 Trac[main] DEBUG: Dispatching <RequestWithSession "POST '/login/xmlrpc'">
2013-10-11 10:57:55,098 Trac[web_ui] DEBUG: LoginModule._remote_user: Authentication attempted for 'None'
2013-10-11 10:57:55,098 Trac[web_ui] DEBUG: LoginModule.authenticate: Set 'REMOTE_USER' = 'None'
2013-10-11 10:57:55,112 Trac[filter] DEBUG: HTTPAuthFilter: Authentication okay for builder
2013-10-11 10:57:55,112 Trac[session] DEBUG: Retrieving session for ID 'builder'
2013-10-11 10:57:55,128 Trac[main] DEBUG: Negotiated locale: None -> en_US
2013-10-11 10:57:55,628 Trac[svn_fs] DEBUG: Subversion bindings imported
2013-10-11 10:57:55,644 Trac[api] INFO: Synchronized '(default)' repository in 0.55 seconds
2013-10-11 10:57:55,644 Trac[web_ui] DEBUG: RPC incoming request of content type 'text/xml' dispatched to <tracrpc.xml_rpc.XmlRpcProtocol object at 0x01B37350>
2013-10-11 10:57:55,644 Trac[web_ui] DEBUG: RPC(XML-RPC) call by 'builder'
2013-10-11 10:57:55,644 Trac[xml_rpc] DEBUG: RPC(xml) call by 'builder', method 'ticket.query' with args: ('status!=asdfasdf&max=0',)
2013-10-11 10:57:55,644 Trac[api] DEBUG: action controllers for ticket workflow: ['ConfigurableTicketWorkflow']
2013-10-11 10:57:55,660 Trac[web_ui] DEBUG: RPC(XML-RPC) call by 'builder' ticket.query
2013-10-11 10:57:55,676 Trac[query] DEBUG: Count results in Query: 1
2013-10-11 10:57:55,676 Trac[xml_rpc] DEBUG: RPC(xml) 'ticket.query' result: [1]
2013-10-11 11:01:10,332 Trac[main] DEBUG: Dispatching <RequestWithSession "POST '/login/xmlrpc'">
2013-10-11 11:01:10,332 Trac[web_ui] DEBUG: LoginModule._remote_user: Authentication attempted for 'None'
2013-10-11 11:01:10,332 Trac[web_ui] DEBUG: LoginModule.authenticate: Set 'REMOTE_USER' = 'None'
2013-10-11 11:01:10,332 Trac[filter] DEBUG: HTTPAuthFilter: Authentication okay for builder
2013-10-11 11:01:10,332 Trac[session] DEBUG: Retrieving session for ID 'builder'
2013-10-11 11:01:10,348 Trac[main] DEBUG: Negotiated locale: None -> en_US
2013-10-11 11:01:10,598 Trac[api] INFO: Synchronized '(default)' repository in 0.27 seconds
2013-10-11 11:01:10,598 Trac[web_ui] DEBUG: RPC incoming request of content type 'text/xml' dispatched to <tracrpc.xml_rpc.XmlRpcProtocol object at 0x01B37350>
2013-10-11 11:01:10,598 Trac[web_ui] DEBUG: RPC(XML-RPC) call by 'builder'
2013-10-11 11:01:10,598 Trac[xml_rpc] DEBUG: RPC(xml) call by 'builder', method 'ticket.create' with args: ('smazat', 'smazat', {'status': 'new', 'severity': 'highest', 'reporter': 'smazat', 'cc': '', 'resolution': 'invalid', 'milestone': '', 'component': '', 'summary': 'smazat', 'priority': 'blocker', 'keywords': '', 'version': '', 'time': <DateTime '20131011T11:01:12' at 1b32c60>, 'owner': '', 'type': 'defect', 'description': 'smazat'})
2013-10-11 11:01:10,614 Trac[web_ui] DEBUG: RPC(XML-RPC) call by 'builder' ticket.create
2013-10-11 11:01:10,614 Trac[web_ui] ERROR: RPC(XML-RPC) Error
Traceback (most recent call last):
  File "build\bdist.win32\egg\tracrpc\web_ui.py", line 158, in _rpc_process
    result = (XMLRPCSystem(self.env).get_method(method_name)(req, args))[0]
  File "build\bdist.win32\egg\tracrpc\api.py", line 197, in __call__
    result = self.callable(req, *args)
  File "build\bdist.win32\egg\tracrpc\ticket.py", line 162, in create
    t[k] = v
  File "c:\docume~1\kubest~1.e-b\locals~1\temp\easy_install-7rt2br\Trac-1.0.1-py2.7-win32.egg.tmp\trac\ticket\model.py", line 160, in __setitem__
    value = value.strip()
ServiceException: 'datetime.datetime' object has no attribute 'strip'
2013-10-11 11:01:10,614 Trac[xml_rpc] ERROR: 'datetime.datetime' object has no attribute 'strip'
2013-10-11 11:01:10,614 Trac[xml_rpc] ERROR: Traceback (most recent call last):
  File "build\bdist.win32\egg\tracrpc\web_ui.py", line 158, in _rpc_process
    result = (XMLRPCSystem(self.env).get_method(method_name)(req, args))[0]
  File "build\bdist.win32\egg\tracrpc\api.py", line 197, in __call__
    result = self.callable(req, *args)
  File "build\bdist.win32\egg\tracrpc\ticket.py", line 162, in create
    t[k] = v
  File "c:\docume~1\kubest~1.e-b\locals~1\temp\easy_install-7rt2br\Trac-1.0.1-py2.7-win32.egg.tmp\trac\ticket\model.py", line 160, in __setitem__
    value = value.strip()
ServiceException: 'datetime.datetime' object has no attribute 'strip'


comment:8 Changed 11 years ago by osimons

Ah. Re-reading the docs and code again, I see that you are indeed doing time wrong. This attribute has no meaning in Trac before the ticket actually exists, and therefore the call signature has a when argument to use for this special value. Also note that you need to be TRAC_ADMIN to be allowed to set a custom creation time.

Here is the actual current code for reference:

def create(self, req, summary, description, attributes={}, notify=False, when=None):
    """ Create a new ticket, returning the ticket ID.
    Overriding 'when' requires admin permission. """
    t = model.Ticket(self.env)
    t['summary'] = summary
    t['description'] = description
    t['reporter'] = req.authname
    for k, v in attributes.iteritems():
        t[k] = v
    t['status'] = 'new'
    t['resolution'] = ''
    # custom create timestamp?
    if when and not 'TICKET_ADMIN' in req.perm:
        self.log.warn("RPC ticket.create: %r not allowed to create with "
                "non-current timestamp (%r)", req.authname, when)
        when = None
    t.insert(when=when)
    if notify:
        try:
            tn = TicketNotifyEmail(self.env)
            tn.notify(t, newticket=True)
        except Exception, e:
            self.log.exception("Failure sending notification on creation "
                               "of ticket #%s: %s" % (t.id, e))
    return t.id

comment:9 Changed 11 years ago by kubes@…

Briliant!!! That solve my issue. Thank you very much.

I had old signatures (from Trac 0.12) and there was string parameter, not DateTime. Then I regenerated method signatures and used the "when" parameter.

I suppose it would be more clear if the parameter names are consistant. The name of the time parameter is "time" in the ticket.get method and "when" in the ticket.create/update method.

Secondly what was confusing, that the time parameter can be obtain via dictionary, but cannot be set via dictionary, but via method parameter (which has different name).

comment:10 Changed 10 years ago by osimons

Resolution: worksforme
Status: newclosed

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain osimons.
The resolution will be deleted. Next status will be 'reopened'.

Add Comment


E-mail address and name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.