The first hurdle with creating the iPhone client is to overcome the SQL Server CE synchronization of the current ClickMobile client. In this post, I describe in short the decision I took in the replacement of this technology.
The current ClickMobile solution is storing all the data and the communication into local SQL Server CE database. Then the client taking advantage of the built-in technology of SQL Server CE to synchronize the local instance with the centralized instance on the remote server.
SQL Server CE includes a special "Remote Data Access" object, that communicate with the central SQL Server over HTTP. This object exposes special PUSH and PULL methods for table synchronization. On the server, ClickMobile is an agent, which runs in the background and poll the SQL Server database. This agent is responsible for processing the "requests" records.
I want to have the iPhone client communicating using the same "channel". So I started by creating a WebService with PUSH and PULL methods. This WebService allows the client to perform the table synchronization without running SQL Server CE.
It is now the time to create the client side component. I think it should be a SQLite to SOAP and SOAP to SQLite gateway. The SOAP messages are proprietary and are not following any standard (except of being SOAP messages). Following this client side development, I may also develop compression to save on bandwidth.
Well, off to work now...
Sunday, November 22, 2009
Friday, November 6, 2009
ClickMobile for the iPhone
I have decided to develop an engineer client application for the iPhone (similar to ClickMobile). I guess I will have this blog updated with my progress from time to time.
Here is a short status of what I did already:
1. It took me few days to have a running environment of Service Optimization on VirtualBox (including both server side of ClickMobile, as well as PC client of ClickMobile).
2. It took me another day to bridge the host OS and the guest OS securely, so the iPhone Simulator can talk to the WebService on the virtual machine.
3. Another day I spent on creating small iPhone app, that can send SOAP message to WebService in the ClickMobileSync directory, and parse the SOAP response.
Now, since there is no SQL Server Mobile for iPhone, I will need to go with custom solution (hope this wont be too long).
If you have any comments, requests, or priorities for such application, please leave a blog comment or call me. I would be more than happy to hear from you ;-)
Here is a short status of what I did already:
1. It took me few days to have a running environment of Service Optimization on VirtualBox (including both server side of ClickMobile, as well as PC client of ClickMobile).
2. It took me another day to bridge the host OS and the guest OS securely, so the iPhone Simulator can talk to the WebService on the virtual machine.
3. Another day I spent on creating small iPhone app, that can send SOAP message to WebService in the ClickMobileSync directory, and parse the SOAP response.
Now, since there is no SQL Server Mobile for iPhone, I will need to go with custom solution (hope this wont be too long).
If you have any comments, requests, or priorities for such application, please leave a blog comment or call me. I would be more than happy to hear from you ;-)
Monday, September 28, 2009
Geographic Domain in Client Add-in
When writing add-in to the ClickSchedule client, I was trying to get the full navigation tree from ClickSchedule client. I have made this short code:
The problem with this option is the requirement for the user to have one or more districts loaded in the client, prior to opening this add-in. So, here is the second option:
The second option is longer, but works without loaded districts.
' Option 1 - using the client's loaded domain
Public Function DoAddIn( _
ByRef objCallingApp As Object, _
ByRef objCallingDoc As Object, _
ByRef lDocType As Integer, _
ByRef varSelectedEngineers As Object, _
ByRef varSelectedTasks As Object, _
ByRef varSelectedAssignments As Object) _
As Boolean
' Get the client domain navigation tree
Dim navObj As Object = _
CType(CType(objCallingApp, _
W6BFClient.W6BFClient).Domain, _
W6BFClient.Domain).NavigationTree
If Not IsArray(navObj) Then Return False
Dim navArr As System.Array = navObj
' Request full navigation tree
Dim request As XmlDocument = New XmlDocument
request.LoadXml( _
"<SXPServerGetTree Revision=""7.5.0"">" & _
"<NavigationTree/></SXPServerGetTree>")
Dim ids As XmlNode = _
request.DocumentElement.FirstChild
' Set the collection ids
Dim first As System.Array = navArr(0)
For index As Integer = _
first.GetUpperBound(0) To 2 Step -3
ids.AppendChild(request.CreateElement( _
"CollectionID")).InnerText = _
first(index).ToString()
Next
' Create connection object (this is add-in, _
' connection already open by the client)
Dim sxp As W6Logon.Connection = _
New W6Logon.Connection
Dim response As XmlDocument = sxp.Send(request)
End Function
The problem with this option is the requirement for the user to have one or more districts loaded in the client, prior to opening this add-in. So, here is the second option:
' Option 2 - not using the client's loaded domain
Public Function DoAddIn( _
ByRef objCallingApp As Object, _
ByRef objCallingDoc As Object, _
ByRef lDocType As Integer, _
ByRef varSelectedEngineers As Object, _
ByRef varSelectedTasks As Object, _
ByRef varSelectedAssignments As Object) _
As Boolean
' Get the client app manager
Dim clientAppManager As _
W6BFClient.IW6BFClientAppManager = _
CType(objCallingApp, _
W6BFClient.IW6BFClientAppManager)
' Request admin setting
Dim requestAdmin As XmlDocument = New XmlDocument
requestAdmin.LoadXml( _
"<SXPServerGetObjects Revision=""7.5.0"">" & _
"<ObjectType>UserSetting</ObjectType>" & _
"<Indexes><Distinct>0</Distinct>" & _
"<Index><LowBound><Property>" & _
"<Name>Category</Name>" & _
"<Value>Power Scheduler Client</Value>" & _
"</Property><Property>" & _
"<Name>SubCategory</Name>" & _
"<Value>Administrative Settings</Value>" & _
"</Property></LowBound><HighBound>" & _
"<Property><Name>Category</Name>" & _
"<Value>Power Scheduler Client</Value>" & _
"</Property><Property>" & _
"<Name>SubCategory</Name>" & _
"<Value>Administrative Settings</Value>" & _
"</Property></HighBound></Index></Indexes>" & _
"<RequestedProperties>" & _
"<Item>Key</Item><Item>Owner</Item>" & _
"</RequestedProperties>" & _
"</SXPServerGetObjects>")
Dim responseAdmin As XmlDocument = SendSxp( _
"SXPServerGetObjects Administrative" & _
" Settings", requestAdmin)
' Get the template key
Dim template As String = _
clientAppManager.AdminTemplateName.ToLower()
Dim key As Integer = -1
For Each child As XmlNode In _
responseAdmin.DocumentElement.FirstChild.ChildNodes
Dim owner As String = child.SelectSingleNode( _
"Owner").InnerText.ToLower()
If template.Equals(owner) Then
key = CInt(child.SelectSingleNode( _
"Key").InnerText)
Exit For
End If
Next
If (key = -1) Then Return False
' Request template setting
Dim requestTemplate As XmlDocument = _
New XmlDocument
requestTemplate.LoadXml( _
"<SXPServerGetObjects Revision=""7.5.0"">" & _
"<ObjectType>UserSetting</ObjectType>" & _
"<KeySet><Key>" & key.ToString() & _
"</Key></KeySet><RequestedProperties>" & _
"<Item>Key</Item><Item>Body</Item>" & _
"</RequestedProperties></SXPServerGetObjects>")
Dim responseTemplate As XmlDocument = SendSxp( _
"SXPServerGetObjects AdminTemplateName", _
requestTemplate)
' Get the task property ids for the navigation tree
Dim body As XmlDocument = New XmlDocument
body.LoadXml( _
responseTemplate.DocumentElement.FirstChild. _
FirstChild.SelectSingleNode( _
"Body").InnerText)
Dim navigationProperties As XmlNode = _
body.DocumentElement.SelectSingleNode( _
"SOFTWARE/IET/W-6BreakFix/Client/" & _
"Administration/Navigation")
' Request task scheme
Dim requestTaskScheme As XmlDocument = _
New XmlDocument
requestTaskScheme.LoadXml( _
"<SXPServerGetCollectionsScheme " & _
"Revision=""7.5.0"">" & _
"<Collections><Collection><ID>2</ID>" & _
"</Collection></Collections>" & _
"</SXPServerGetCollectionsScheme>")
Dim responseTaskScheme As XmlDocument = SendSxp( _
"SXPServerGetCollectionsScheme", _
requestTaskScheme)
' Request full navigation tree
Dim request As XmlDocument = New XmlDocument
request.LoadXml( _
"<SXPServerGetTree Revision=""7.5.0"">" & _
"<NavigationTree/></SXPServerGetTree>")
Dim ids As XmlNode = _
request.DocumentElement.FirstChild
' Set the collection ids
For index As Integer = _
1 To navigationProperties.ChildNodes.Count
Dim propertyID As String = _
navigationProperties.SelectSingleNode( _
"Level" & index.ToString() & _
"/FieldNumber").InnerText.Replace("""", "")
Dim collectionID As String = _
responseTaskScheme.DocumentElement. _
SelectSingleNode( _
"Collections/Collection/Attributes" & _
"/Attribute[ID='" & _
propertyID & _
"']/KeyTypeInfo/ObjectType").InnerText
ids.AppendChild(request.CreateElement( _
"CollectionID")).InnerText = collectionID
Next
' Create connection object (this is add-in, _
' connection already open by the client)
Dim sxp As W6Logon.Connection = _
New W6Logon.Connection
Dim response As XmlDocument = sxp.Send(request)
End Function
The second option is longer, but works without loaded districts.
Tuesday, August 4, 2009
Cannot show owner of parent of UserSetting object
This is a small limitation of ClickAnalyze infrastructure. While you can define a report on UserSetting collection, which includes both Owner property as well as ParentUserSetting.Owner, the result is that the Owner property of the object is logged twice and the parent owner is not logged at all.
Saturday, July 18, 2009
Custom Calculator can skip SetValue
One of the less familiar features of ClickAnalyze infrastructure is the handling of a permutation full of nulls.
To be more clear, let say that a report is made of Time and Geography dimensions, and then 2 custom calculators. Assume that for the permutation "July 19, 2009", "North-East", "Boston" both calculators get short list of assignments. Both calculator find that the total they need to calculate is 0 (zero). This can happen due to the fact that this is Sunday and the calculators are considering the engineers' calendars.
So, let say that both calculators will include code that skip the SetValue method, when the value is zero:
The ClickAnalyze infrastructure identifies this as permutation with no data, means that all calculators skip the SetValue. By default, each report includes advanced property "Include Permutation With No Data" set to False. This means that our permutation will not be written to the database. Changing the property to True will cause the permutation to be written with nulls. This of-course might result with many more records full of redundant data.
To be more clear, let say that a report is made of Time and Geography dimensions, and then 2 custom calculators. Assume that for the permutation "July 19, 2009", "North-East", "Boston" both calculators get short list of assignments. Both calculator find that the total they need to calculate is 0 (zero). This can happen due to the fact that this is Sunday and the calculators are considering the engineers' calendars.
So, let say that both calculators will include code that skip the SetValue method, when the value is zero:
If Not (totalSeconds = 0) Then
pCalculatorsRowItem.SetValue(0, totalSeconds)
End If
The ClickAnalyze infrastructure identifies this as permutation with no data, means that all calculators skip the SetValue. By default, each report includes advanced property "Include Permutation With No Data" set to False. This means that our permutation will not be written to the database. Changing the property to True will cause the permutation to be written with nulls. This of-course might result with many more records full of redundant data.
Friday, July 17, 2009
Table with NoRows
While trying to understand why a table or matrix is not rendered in some reports I recently made (under Reporting Services 2005), I found that actually there was no data available for the table.
The simple solution for this problem was to set the NoRows property of the table or the matrix.
Why the default value of this property is empty string? I don't know! but it would be much easier if it wasn't.
The simple solution for this problem was to set the NoRows property of the table or the matrix.
Why the default value of this property is empty string? I don't know! but it would be much easier if it wasn't.
Saturday, June 20, 2009
Should I use the Resource Type measure
(Q) Why do I get records full of NULLs when I'm using the Resource Type measure?
(A) This question is about ClickAnalyze Reporting for ClickSchedule. More specifically it is raised in regards to the Resource Schedule report.
By now, you probably noticed that engineers with assignments have one record per assignment, but engineers with no assignments have one record full of NULL values. The NULL values are at the columns, which hold the assignment and task details.
Why there is no such record for engineer with assignments? Why engineer with no assignments, don't have zero records? Meaning that this NULLs record should not be written at all!
Well, the answer is in the question: the Resource Type measure.
When the reporting infrastructure initialize the calculators (measures), each calculator declares what type of objects it needs. In the first screen-shot we can see that Resource Type asks for Engineer objects (see the Conditions and Data Filter properties).
The "Task" measure is a Get Schedule measure type. It's also requires Engineer objects, but it returns matrix of values.
Here is the place to explain what is this "matrix of values": Each calculator defines list of columns that it can populate. The Get Schedule calculator defines this list from the parameters entered by the user (Task Properties and Assignment Properties). Assuming the list is made of 5 task properties and 3 assignment properties, then the calculator defines 8 sub-columns. Each time the infrastructure is calling the calculator with Engineer objects, it prepares array with 8 NULL values, and the calculator can write values into those places. But here comes the special part, the Get Schedule calculator fills the 8 values from the first task-assignment pair, and then calls the infrastructure to AddRow. The infrastructure duplicate all values of the preceding dimensions and measures and prepare new array of NULL values, so the calculator can now start writing the second row of the matrix, and so on.
When the Get Schedule calculator have no assignments to process, it leaves the array full of NULL values and exit. If the infrastructure finds that all measures are returning only NULL values, then the record is not written to the database.
BUT, in our case, the Resource Type measure always write a string value, so we get this record full of NULL values (the NULL values, which the Get Schedule measure did not fill).
We should note that by design, the special "matrix measures" should be the only measure in the report. It is by coincidence that the infrastructure supports one "matrix measure" as the last measure, even when it is preceding with other simple measures.
I would expect this Resource Type calculator to be removed. The same can be achieved by adding the properties to the "Resource" dimension. In this way, records full of NULL values, will not be written to the database.
(A) This question is about ClickAnalyze Reporting for ClickSchedule. More specifically it is raised in regards to the Resource Schedule report.
By now, you probably noticed that engineers with assignments have one record per assignment, but engineers with no assignments have one record full of NULL values. The NULL values are at the columns, which hold the assignment and task details.
Why there is no such record for engineer with assignments? Why engineer with no assignments, don't have zero records? Meaning that this NULLs record should not be written at all!
Well, the answer is in the question: the Resource Type measure.
When the reporting infrastructure initialize the calculators (measures), each calculator declares what type of objects it needs. In the first screen-shot we can see that Resource Type asks for Engineer objects (see the Conditions and Data Filter properties).
The "Task" measure is a Get Schedule measure type. It's also requires Engineer objects, but it returns matrix of values.
Here is the place to explain what is this "matrix of values": Each calculator defines list of columns that it can populate. The Get Schedule calculator defines this list from the parameters entered by the user (Task Properties and Assignment Properties). Assuming the list is made of 5 task properties and 3 assignment properties, then the calculator defines 8 sub-columns. Each time the infrastructure is calling the calculator with Engineer objects, it prepares array with 8 NULL values, and the calculator can write values into those places. But here comes the special part, the Get Schedule calculator fills the 8 values from the first task-assignment pair, and then calls the infrastructure to AddRow. The infrastructure duplicate all values of the preceding dimensions and measures and prepare new array of NULL values, so the calculator can now start writing the second row of the matrix, and so on.
When the Get Schedule calculator have no assignments to process, it leaves the array full of NULL values and exit. If the infrastructure finds that all measures are returning only NULL values, then the record is not written to the database.
BUT, in our case, the Resource Type measure always write a string value, so we get this record full of NULL values (the NULL values, which the Get Schedule measure did not fill).
We should note that by design, the special "matrix measures" should be the only measure in the report. It is by coincidence that the infrastructure supports one "matrix measure" as the last measure, even when it is preceding with other simple measures.
I would expect this Resource Type calculator to be removed. The same can be achieved by adding the properties to the "Resource" dimension. In this way, records full of NULL values, will not be written to the database.
Tuesday, June 16, 2009
Product vs Core. Why use low level API ?
Sometimes I wish that product API would not raise errors, but just return numeric result with the error number. This is what happening in the core API.
For example, the W6PTimeIntervals is a simple product wrapper for the W6TimeIntervals core class. Interesting is the fact that W6TimeInterval is a core class, which is used in the product without a wrapper.
However, the following short example of code is an implementation of a method, which is sometimes needed when dealing with time intervals. The error handling is general and there is no need to deal with "end of list" error:
For example, the W6PTimeIntervals is a simple product wrapper for the W6TimeIntervals core class. Interesting is the fact that W6TimeInterval is a core class, which is used in the product without a wrapper.
However, the following short example of code is an implementation of a method, which is sometimes needed when dealing with time intervals. The error handling is general and there is no need to deal with "end of list" error:
Private Sub UnSetTimeIntervals( _
ByVal intervals As W6PTimeIntervals, _
ByVal unset As W6PTimeIntervals)
Try
Dim W6RC_MDL_TIME_INTERVALS_END_OF_LIST _
As Integer = 4120
Dim rc As Integer = 0
Dim timeInterval As W6TimeInterval
= New W6TimeInterval
' UnSet older intervals from the newer intervals,
' so only the new interval remains
rc = unset.RefW6TimeIntervals.GetFirst( _
timeInterval)
Do While (CatchRcExclude(rc, _
W6RC_MDL_TIME_INTERVALS_END_OF_LIST))
CatchRC( _
intervals.RefW6TimeIntervals.UnSetTimeInterval _
(timeInterval))
rc = unset.RefW6TimeIntervals.GetNext( _
timeInterval)
Loop
Catch ex As Exception
Call GeneralErrorHandling("UnSetTimeIntervals")
End Try
End Sub
Private Function CatchRcExclude( _
ByVal rc As Integer, _
ByVal exclude As Integer) As Boolean
If (rc = exclude) Then Return False
CatchRC(rc)
Return True
End Function
Private Sub CatchRC(ByVal rc As Integer)
If (rc = 0) Then Exit Sub
Dim errObject As W6ShPError = _
W6ShPErrorUtilities.W6CreateCoreError( _
rc, "", True, True)
End Sub
Tuesday, June 9, 2009
Add-in for custom report
When I was writing a custom ClickAnalyze report, I did not expect that the ClickSchedule client's wizard, which opens the report, will cause any troubles. But it was no ordinary report, and as such it did not include geography dimension (no region and no district). The wizard did not handle this well and actually I had to develop a simple add-in.
In order to create the add-in, I just needed to send SXP, Create the out-of-the-box form with the report viewer, and show it (well, it might be a bit more). And here is the code:
In order to create the add-in, I just needed to send SXP, Create the out-of-the-box form with the report viewer, and show it (well, it might be a bit more). And here is the code:
' This class should be visible to COM
Public Class W6CustomReportAddin
Public Function DoAddIn( _
ByRef objCallingApp As Object, _
ByRef objCallingDoc As Object, _
ByRef lDocType As Integer, _
ByRef varSelectedEngineers As Object, _
ByRef varSelectedTasks As Object, _
ByRef varSelectedAssignments As Object) _
As Boolean
Try
' Send SXP request and get the response
Dim request As Xml.XmlDocument = _
New Xml.XmlDocument
request.LoadXml( _
"<SXPServerReportProcess><Report>" & _
"<Name>Custom Report</Name>" & _
"</Report></SXPServerReportProcess>")
Dim conn As W6Logon.Connection = _
New W6Logon.Connection
Dim response As Xml.XmlDocument = _
conn.Send(request)
' Create the report viewer dialog and set
' the report properties
Dim dialog As W6ReportsClient.frmMainForm = _
New W6ReportsClient.frmMainForm( _
W6ReportsClient.ProductType.ClickSchedule)
Dim dataManager As _
W6ReportsClient.CW6DataManager = _
W6ReportsClient.CW6DataManager.GetInstance()
dialog.ReportsServerURL = _
dataManager.ReportServerURL
dialog.ReportPath = "/" & _
dataManager.RootFolder & "/" & _
dataManager.ClickScheduleFolderName & _
"/Day/Custom Report"
' Create client domain and set the instance id
Dim domain As _
W6ReportsClient.CW6ClientDomain = _
New W6ReportsClient.CW6ClientDomain( _
W6ReportsClient.ProductType.ClickSchedule)
domain.InstanceID = _
response.DocumentElement.SelectSingleNode( _
"InstanceID").InnerText
' Set the title and build the report
dialog.AddReportNameToTitle("Custom Report")
dialog.BuildReport(domain)
' Show the dialog as modal form
dialog.ShowDialog()
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.OkOnly, "Error")
End Try
End Function
End Class
Monday, June 8, 2009
When it's not DueDate anymore
Today we will focus on 2 "Click" products and more specifically, on one interaction point between them: ClickSchedule and ClickAnalyze.
I recently seen more and more projects that are using other Task properties than DueDate for fetching unscheduled tasks. Lets assume we have new user defined property (UDP), which represents the date that an unscheduled task should be scheduled (many times it is called OptimizeOnDate). I would like ClickAnalyze to use this new UDP instead of the DueDate in default conditions.
First, I change the setting of the Service Optimization Administration snap-in. I want to be able to edit the XML in the Body property of a specific setting object.
You can see I added the standard XML editing to Setting object with Category="Reports" and SubCategory="Components".
Now I can open the Components setting using standard XML editing, and manipulate the Body property directly.
I expand the components until I found the "Time Resolution" dimension.
You can see that it includes additional node after the "Type Specific Information" comment.
I expand the third OQL condition for the tasks collection.
Just to remind you, OQL stands for Object Query Language. This is the XML that is used in the group objects of the "Click" products.
I can spot that DueDate property is used twice (see red arrows).
In my customization, the ID of the OptimizeOnDate property is 105. So I changed both places and keep those changes.
Now I'm creating new report for ClickAnalyze Reporting.
I added "Count Objects" measure and named it Tasks.
You can see that the default condition for tasks is no longer on the DueDate property, but on the new OptimizeOnDate property.
You can use the same technique to have new OQL conditions for new collections that include date properties.
I recently seen more and more projects that are using other Task properties than DueDate for fetching unscheduled tasks. Lets assume we have new user defined property (UDP), which represents the date that an unscheduled task should be scheduled (many times it is called OptimizeOnDate). I would like ClickAnalyze to use this new UDP instead of the DueDate in default conditions.
First, I change the setting of the Service Optimization Administration snap-in. I want to be able to edit the XML in the Body property of a specific setting object.
You can see I added the standard XML editing to Setting object with Category="Reports" and SubCategory="Components".
Now I can open the Components setting using standard XML editing, and manipulate the Body property directly.
I expand the components until I found the "Time Resolution" dimension.
You can see that it includes additional node after the "Type Specific Information" comment.
I expand the third OQL condition for the tasks collection.
Just to remind you, OQL stands for Object Query Language. This is the XML that is used in the group objects of the "Click" products.
I can spot that DueDate property is used twice (see red arrows).
In my customization, the ID of the OptimizeOnDate property is 105. So I changed both places and keep those changes.
Now I'm creating new report for ClickAnalyze Reporting.
I added "Count Objects" measure and named it Tasks.
You can see that the default condition for tasks is no longer on the DueDate property, but on the new OptimizeOnDate property.
You can use the same technique to have new OQL conditions for new collections that include date properties.
Sunday, June 7, 2009
Starting new developers blog
For long time I wanted to start blogging about the "Click" products development. I guess that each time I tell someone about a nice feature or a better way to customize and develop for the "Click" products, I want to have a way to share it with more than just one developer. So, from now, this blog will include development ideas and tips for the "Click" products.
Now what if you are not familiar with those products? Go to ClickSoftware.com and learn about those products.
Now what if you are not familiar with those products? Go to ClickSoftware.com and learn about those products.
Subscribe to:
Posts (Atom)