Eduard Lebedyuk · Apr 3, 2018 go to post

If you want to compare two in-memory objects, you can use method generators, there are several related articles and discussions on that:

Simple comparator on GiitHib - note that it's a runtime comparator, therefore slow. Better solution would be method generators.

If you're comparing objects of different classes you need to find their common ancestor class and compare using that.

If you're comparing stored objects you can calculate hashes and compare that.

All in all it's a very complex topic and you need to determine what requirements do you have:

  • Streams?
  • Lists? Arrays? Position change?
  • Loops/relationships strategy
  • How many levels to compare?
  • Different classes? Do they have common superclass?
  • Do you need to compare dynamic objects/objects from unrelated classes?

And design your comparator based on that.

Here's a simple hasher on GitHub.

Eduard Lebedyuk · Apr 3, 2018 go to post

What about discussion?

In my practice issues often include:

  • discussion about implementation strategy
  • references to other issues
  • cross-references to commits
  • test hints
  • start/due dates & time spent
  • milestones
  • current status
  • assigned person(s)

Issues help to collect all this information and make it available later.

Eduard Lebedyuk · Apr 2, 2018 go to post

That works only for CSP context and CSP pages. You can write a wrapper I suppose, but I think it would be easier to just write your own querybuilder code:

ClassMethod Link(server = "www.example.com")
{
    try {
        set cspContext = $data(%request)
        if 'cspContext {
          set %request = {} // ##class(%CSP.Request).%New()  
          set %response = ##class(%CSP.Response).%New()
          set %session = {} //##class(%CSP.Session).%New(-1,0)
        }
        set query("param") = 1
        set page = "/abcd.csp"
        set url = ##class(%CSP.Page).Link(page,.query)
        set url = $replace(url, page, server)
        write url
        kill:'cspContext %request,%response,%session
    } catch {
        kill:'$g(cspContext) %request,%response,%session
    }
}

With querybuilder:

ClassMethod Link(server = "www.example.com")
{
    set query("param") = 1

    set data = ""
    set param = $order(query(""),1,value)
    while (param'="") {
        set data=data _ $lb($$$URLENCODE(param)_"="_$$$URLENCODE(value))
        set param = $order(query(param),1,value)          
    }
    write server _ "?" _ $lts(data, "&")
}
Eduard Lebedyuk · Mar 28, 2018 go to post

You're doing two separate operations:

  1. Syncing the data
  2. Syncing the cube

They can both be system tasks with one task dependent on other or even one task altogether.

If you're using persistent objects to store data you can specify DSTIME:

Parameter DSTIME = "AUTO";

and  ^OBJ.DSTIME would be maintained automatically.

UPD. Read your other comment. DSTIME  is relevant only for syncing. It does not affect full build behaviour.

Eduard Lebedyuk · Mar 24, 2018 go to post

You can change user password in System Management Portal -> Menu -> Users.

Note that if you installed Cache under minimal security it may be easier just reinstall Cache with Normal/Locked Down security.

Eduard Lebedyuk · Mar 22, 2018 go to post

For higher performance it's better to keep the data in InterSystems platform and sync it with remote db periodically.

To download the data via xDBC you have two main approaches:

  • Interoperability (Ensemble) SQL inbound adapter
  • "Raw" access via %SQLGatewayConnection or %Net.Remote.Java.JDBCGateway

Interoperability approach is better as it solves most problems and user should just enter the query, etc. "Raw" can be faster and allows for fine-tuning.

Now, to keep data synced there are several approaches available:

  • If source table has UpdatedOn field, track it and get rows updated only after last sync.
  • Journals: some databases provide parsable journals, use them to determine which rows changed in relevant tables.
  • Triggers: sometimes source table has triggers (i.e. Audit) which while do not provide explicit UpdatedOn field nonetheless can be used to determine row update time.
  • Hashing: hash incoming data and save the hash, update the row only if hash changed.

If you can modify source database - add UpdatedOn field, it's the best6 solution.

Linked tables allow you not to store data permanently, but the cube would be rebuilt each time. With other approaches syncing cube is enough.

Also check this guide on DeepSee Data Connectors.

Eduard Lebedyuk · Mar 18, 2018 go to post

I have no real answer to the logon issue

What about custom login pages?

<html>
    <head>
        <title>cUSTOM LOGIN PAGE</title>
    </head>
    <body>
        <div style="">
            <form name="loginForm" class="form-signin" method="post" action="#("./index.csp")#">
                <p id="caption">Registration system</p>
                <input name="CacheLogin" value="1" type="hidden">
                <input id="CacheUserName" type="text" class="input-block-level" name="CacheUserName" placeholder="Login" value="_SYSTEM">
                <input type="password" class="input-block-level" name="CachePassword" placeholder="Password" value="SYS">
                <button class="btn btn-small btn-primary" type="submit" style="font-size: 1em;">Login</button>
            </form>
        </div>
    </body>
</html>
Eduard Lebedyuk · Mar 15, 2018 go to post

If you're using WYSWIG editor you can specify a cut - part of text that's seen as a post preview:

Here's how it looks:

How to do that in Markdown?

BTW, advice: don't switch MD article into WYSWIG, you'll lose the formatting.

Eduard Lebedyuk · Mar 14, 2018 go to post

When you're using Import, one record that imports - is it correct (corresponds to one string in csv)?

Eduard Lebedyuk · Mar 13, 2018 go to post

Use parameters instead of building the query manually. Everything would be escaped automatically.

Eduard Lebedyuk · Mar 13, 2018 go to post

You're importing

jitPod.Api.toServer.logon

and that's whats imported. I think you skipped step 5, where you replace all reference to Apiwith api.

Eduard Lebedyuk · Mar 13, 2018 go to post

Studio is less important here.

What does this return:

write ##class(%Dictionary.CompiledClass).%ExistsId("jitPod.Api.toServer.logon"),!
write ##class(%Dictionary.CompiledClass).%ExistsId("jitPod.api.toServer.logon"),!
zwrite ^oddPKG($zcvt("itPod.api", "U"))
Eduard Lebedyuk · Mar 13, 2018 go to post

1. Export Api package.

2. Uncompile and Delete all classes from Api package regardless of the case.

3. Delete package with

write $System.OBJ.DeletePackage("package name") 

4. Check if there's anything related to jitPod.Api package left in:

zw ^oddPKG

Delete ONLY entries related to your package.

5. In exported code replace Api with api for all classes. 

6. Restart Cache.

7. Import classes.

Eduard Lebedyuk · Mar 13, 2018 go to post

That would work, but I'm looking for a solution without user code.

Some system OutputToDevice method.

Eduard Lebedyuk · Mar 13, 2018 go to post

Pattern can be used:

 Property GUID As %String(PATTERN = "8AN1""-""4AN1""-""4AN1""-""4AN1""-""12AN") [ InitialExpression = {$SYSTEM.Util.CreateGUID()} ];

And A can be replaced with L or U to store GUIDs in one case.

Eduard Lebedyuk · Mar 13, 2018 go to post

I'm calling load code like this:

csession ensemble "##class(isc.git.GitLab).load()"

ObjectScript:

/// Do a full load
/// do ##class(isc.git.GitLab).load()
ClassMethod load()
{
    try {
        set dir = "/src"
        do ..log("Importing dir " _ dir)
        do $system.OBJ.ImportDir(dir, "*.xml", "c", .errors, 1)
        throw:$get(errors,0)'=0 ##class(%Exception.General).%New("Load & compile error")
        halt
    } catch ex {
        write !,$System.Status.GetErrorText(ex.AsStatus()),!
        do $system.Process.Terminate(, 1)
    }
}

This way process terminates abnormally on error:

do $system.Process.Terminate(, 1)

And it's usually reported as error in CI/CD systems.

Additionally you can grep for "Load & compile error".

I'm currently writing a series of articles on continuous delivery, check it out (latest part).

Eduard Lebedyuk · Mar 12, 2018 go to post

I can't see the image. But it should be the last Item in a first part of the View menu. It's also the only item of a first part of the View menu with a dropdown.

Eduard Lebedyuk · Mar 12, 2018 go to post

What version of Cache does it require to work?

If think it requires 2016.1 but 2016.2+ would be preferable.

I notice in the readme that it says to create a / web application. Did you literally mean "/"?

You can create non-root (/something) web application, that should not be an issue on windows clients iirc.

Read-only office documents

Are OPTIONS requests processed correctly? Check OnHandleOptionsRequest method - you'll need to write a method that processes all OPTION requests and returns relevant headers.

If the problem with read-only Word files persists, it can be fixed by one of the above:

Additionally I recommend:

  • Installing 2017.2 (anything 2016.2+) separately and checking that it does work
  • Installing Wireshark and capturing a sample exchange on 2014.1 and 2017.2
  • Compare these two sessions to understand what fails on 2014.1
Eduard Lebedyuk · Mar 12, 2018 go to post

Are you sure the problem is with the BS? Does visual trace stops there?

Is your target BP compiled?

Eduard Lebedyuk · Mar 12, 2018 go to post

Good article.

If you have BO with one message route in message map you can remove the message map altogether and name your method OnMessage - it would be called automatically.

Eduard Lebedyuk · Mar 9, 2018 go to post

Can I still add code in Business Studio and it maintain integrity to be able to still open in the BPL editor?

It's easier to use one tool to modify BPL.  <assign> is available in UI so you can add it without writing XML at all.

  then the BPL hung.

I tested that behavior in BS only and it works there fine there.  Maybe it causes BP to break. You can try to set SessionId just before sending request to other BP or BO and then reinstating old SesionId  value back. That way persistent  session value on disc would stay the same and BP can maybe survive unload from memory.

My recommendation is to iterate over result set in BS rather than BP, then send each new snapshot (preferably preprocessed  in BS) to BP with new session for each object.