John Kumpf · Dec 7, 2018 go to post

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="CACHE2010.Build">
<TimeCreated>64952,32335.711823</TimeCreated>

<Parameter name="SrcVer">
<Description>
Location and Revision of this file in Perforce (Auto-updating)</Description>
<Default>$Id: //custom_ccrs/_common/tools/CCRToolsBuild/BuildManifests/Cache2010/Build.xml#1 $</Default>
</Parameter>

<XData name="BuildManifest">
<Description>
This is a build manifest. Here we describe build steps in XML format.</Description>
<Data><![CDATA[
<Manifest>

<!-- Change into namespace -->
<Namespace Name="CCRClientTools" Ensemble="0" Code="CCRTOOLS-CODE" Data="CCRTOOLS">
<Configuration>
<!-- Have  database for code -->
<Database Name="CCRTOOLS-CODE" Dir="c:\intersystems\cache\mgr\ccrtools-code\" Create="overwrite" InitialSize="200" ExpansionSize="60" />
</Configuration>

</Namespace>

</Manifest>
]]></Data>
</XData>

<Method name="Build">
<Description>
This method is called to pass parameters into generated build method</Description>
<ClassMethod>1</ClassMethod>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
     Set pLogLevel=3
     Set sc=..RunBuildManifest(.vars, .pLogLevel)

     Quit sc
]]></Implementation>
</Method>

<Method name="RunBuildManifest">
<Description>
This is a standard %Installer method generator whose code is generated by XGL.</Description>
<Internal>1</Internal>
<ClassMethod>1</ClassMethod>
<CodeMode>objectgenerator</CodeMode>
<FormalSpec><![CDATA[&pVars,pLogLevel:%Integer,pInstaller:%Installer.Installer]]></FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
     #; Let our XGL document generate code for this method.
     quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "BuildManifest")
]]></Implementation>
</Method>
</Class>
</Export>

John Kumpf · Dec 7, 2018 go to post

Here the simplest one I have that had the issue if you want too look at it.  Note my answer on what I've discovered (which was enough to solve the issue).

John Kumpf · Jan 15, 2019 go to post

This is great, thanks.  Just implemented it.  Worked like a charm.

Also let me just confirm (and this wasn't needed in my particular implementation): if I call giveMeAFalseMe from Cache when it's returning "true" or "false", Cache will translate "true" into a truthy %Boolean return value, right?

John Kumpf · Feb 13, 2019 go to post

Okay, so if I'm understanding you correctly, even if the user field changes after the initial insertion of the object, the userid field should stay the same?

John Kumpf · Apr 12, 2019 go to post

Ah, using base-href is a great idea, thanks.  A few questions about this:

Why did you rename the files to .csp?

Why are you using ahead of time compilation?

By using:

ng build --base-href = /webapp_name/index.html

I was able to access <cname>/webapp_name/index.html successfully; the index.html file could find the scripts like polyfills.js without me modifying those src arguments by hand.

John Kumpf · Apr 16, 2019 go to post

Thanks a lot.  This worked.

I found the page about this function for anyone who sees this later:

https://irisdocs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Pag…

Would you mind explaining how exactly this particular line is working?  Why do you have to use both JSON_ARRAYARG and JSON_OBJECT and what does that syntax mean?  It looks like it's essentially "selecting" the data from one result set into another as JSON, but I'm not sure how.  What does the JSON_OBJECT call do here, and what does the JSON_ARRAYAGG call do?

John Kumpf · Apr 16, 2019 go to post

Oh okay, I see.  JSON_ARRAYAGG takes whatever results it gets back, and puts them into a JSON array.  Since JSON_OBJECT returns a bunch of results that are json objects, using JSON_ARRAYAGG(JSON_OBJECT( says to take all those json objects, and put them into a json array.

As far as the non select part of the query, it looks like you can specify it with a new "select", or without.  I.e.:

SELECT JSON_ARRAYAGG(JSON_OBJECT('state':Home_State)) FROM Sample.Person WHERE Home_State %STARTSWITH 'A'
SELECT JSON_ARRAYAGG(JSON_OBJECT('state':Home_State)) FROM (select state from Sample.Person WHERE Home_State %STARTSWITH 'A')

Thanks a lot for taking the time to explain.  It's very clear to me what is happening now.

John Kumpf · Apr 23, 2019 go to post

I'm building classes which contain representations of REST resources.  For example, a full (likely for administrators) representation of the "person" resource might contain all fields, and all of them are writable.  A limited (maybe authenticated, but non admin) representation might contain most fields, and some like name and email are writable, but others like employment status are not.  A minimal (maybe for unauthenticated users) representation would contain only a few, read-only fields.

The property parameters would be things like the name for the property we use in the JSON, whether the field is writable, other flags regarding how to represent relationships or object properties, etc.

I think what I'm imagining is very similar to the %XML.PropertyParameters class used by %XML.Adaptor

(If you're probably wondering why I didn't include all this in my original post, I've been discussing it with Tim Leavitt and my ideas are a lot more formulated than they were 2 hours ago.  I suspect that an analogous architecture to what's used with XML will work well.)

John Kumpf · Aug 7, 2019 go to post

You're running into the fact that calling %Get on a dynamic object doesn't return an undefined when data at the index you specify doesn't exist.  Instead, it returns "".

Both the answers you have here are great if you need a generalized solution.  If all you're looking for is a simple work around in a non-general case, all you need to do is check if your %Get returned "",  and if so, kill the value before you pass it in:

ClassMethod passIncompleteJSON(){  set json = ["arg1 val", "arg2 val"]  set arg1 = json.%Get(0)  kill:(arg1="") arg1  set arg2 = json.%Get(1)  kill:(arg2="") arg2  set arg3 = json.%Get(2)  kill:(arg3="") arg3  write ..threeArgs(.arg1, .arg2, .arg3)}ClassMethod threeArgs(a, b, c){  set:($get(c)="") c = "default"  return a_"."_b_"."_c}

Just be sure to pass in the parameters by reference since you're going to be passing in undefined variables.

(This does remove the distinction between undefined and "" though; you wouldn't be able to intentionally pass "" with this solution)

John Kumpf · Aug 28, 2019 go to post

I believe the %JSON import method returns after hitting an error like that, so it's probably more work than you think to have the error give you all fields that are required and empty.

But could you be a little more specific about your use case and exactly what problem you're trying to solve?  There might be a better way.

John Kumpf · Aug 28, 2019 go to post

Oh, okay!  I think the fastest way to get to what you want would be front end form validation.

Whatever you're using to build the UI that leads to this JSON post, have that code mark certain fields as required, and then alert if those aren't filled.  In general, I think in the division of labor that task typically falls to the front end.

In theory, you can write a server method that scans the input before JSON import and then throws a custom exception with all the missing fields, but that's likely more work than doing it on the front end (because the front end will still have to catch that, then assemble the alert message from it anyway).

Also, if you're going to be changing which fields are required a lot, you can (in theory) write a server method which exposes which fields are required, instead of hard-coding that in the front end, but, again, that's likely not worth the effort.

Any of that helpful, or am I misunderstanding the problem?

John Kumpf · Aug 30, 2019 go to post

A few more details about your exact use case would help me give a better answer here.  As for the exact questions you asked, here's an example of the syntax you can use to write a query as part of a class:

Query Orgs() As %SQLQuery(CONTAINID = 1)
{
SELECT ID As OrgID,Name,DisplayNameFROM OrgORDER BY Name
}

As for getting the query result as JSON, I asked a similar question a little while ago, and this discussion was very useful:

https://community.intersystems.com/post/how-do-i-return-json-database-s…

Now, if your real question is something along the lines of "I want to write a REST endpoint that gives me back the result of a class query in Person as JSON", then unfortunately that's a non-trivial problem.  The fastest way would be to write some endpoint, like /basicpersoninfo, and have the REST handler route requests for that endpoint to a method that uses JSON_ARRAYAGG and JSON_OBJECT and dynamic SQL  (discussed in that other thread) to write out the results of that query in JSON.

John Kumpf · Sep 13, 2019 go to post

Can confirm that the %JSON.Adaptor tool is extremely useful!  This was such a great addition to the product.

In Application Services, we've used it to build a framework which allows us to not only expose our persistent classes via REST but also authorize different levels of access for different representations of each class (for example, all the properties, vs just the Name and the Id).  The "Mappings and Parameters" feature is especially useful:

https://irisdocs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Pag…

Also, @Stefan are you writing backwards while you talk?  That's impressive.

John Kumpf · Oct 1, 2019 go to post

We've been using this tool as the backbone of our REST APIs that serve persistent data to Angular UI's.  It's extremely useful and powerful, glad to see it progressing even more.

John Kumpf · Oct 9, 2019 go to post

On a related note, if anyone who's involved in the development of the core product sees this, is the * syntax, where we actually get the name of the oref that was invalid (similar to the *variable syntax we get when a reference is undefined) in the plans for the future?

John Kumpf · Oct 13, 2019 go to post

Would something like that ever make sense if the queries were repeated a lot, across a lot of different applications?  (minus the deleting the query part)

I'd never actually thought of this before.  Have you ever implemented anything like that?  Actually now that I think about it more, it would make more sense to just post the query and have it run (and store if it's not there), rather than getting trying to GET the query first, getting back a 404 or something, and then POSTing it before getting it.

John Kumpf · Oct 13, 2019 go to post

Interesting.  We already have all of our Cache code building, running unit tests, and running code coverage on Jenkins.  My current plan is to see how easy/difficult it is to fit Angular build/test/coverage into that architecture, so that we can have everything in once place.

If there are features we can't have with Jenkins, or if it turns out to be so difficult that it may be worth splitting up our continuous integration across multiple platforms, I'll definitely give this stuff a look.  Thanks for the advice.

John Kumpf · Oct 17, 2019 go to post

That article about pack and link was very helpful and informative.  Thanks for that.

It didn't address what I'm asking, though (unless it did, and I'm wrong).  I don't really need a css build tool, at least I don't think I do.  I literally just want to know how I can get a scss file from one project into the node_modules directory of another via npm.

When I used npm pack on project1 to create a tarball, then installed that tarball into project2, the css file from the project1 wasn't in tarball2's node_modules, and I'd like it to be (so I can import it somewhere in project2).

John Kumpf · Oct 25, 2019 go to post

This is a really good point I hadn't considered.

When I was working with legacy code bases, it was difficult.  However, perhaps I'm blaming the wrong culprit.  Looking back, the main issue is that all the variable names were two random letters (at least to me; by the end I could usually figure out what the two letters were supposed to mean).  I'd be down 15 lines and then have to go back up to a variable's initialization to remember what the heck "cb", "sh", or "ac" actually were, semantically.

That was probably also done to save characters, and, as you said, is far more damaging than set -> s or if -> i.

I also agree that the lack of appropriate spacing makes a huge difference, as appropriate spacing can help show the control flow of the code.  Thanks for commenting this; after reading it, I now think that of all the questionable (in today's world) space saving techniques used in the past, the single letter variable names might be the least offensive.

That said though, with respect to the seeing more at once, s -> set doesn't increase the vertical space at all, so in theory you can see the same number of lines.  Also, I think it's very useful for coding languages to be written in English.  Coding is complex enough as it is, so we might as well take advantage of the fact that we're already completely comfortable with English, and thus we don't need to waste any mental resources during the processing of English string -> semantic meaning.  By using English, we're hijacking our preconceived notions of words like "while" such that the jump to the more complex control flow operator is less severe.  I'd contend it would take far longer than a few hours to be able to really read "w" the way you can read "while".

It's just like learning a new (non-coding) language.  Once I know what the vocabulary words mean, I can competently translate a passage in that language.  But I can't read it yet, with a direct link from the text to the meaning.

John Kumpf · Oct 29, 2019 go to post

That makes a lot of sense that you naturally avoid the abbreviations where the English word carries some significant meaning.  Also, it seems like you naturally avoid abbreviations where the abbreviation carries a sort of misleading semantic meaning. With $case vs $c, $c at first glance looks like some sort of character function, whereas $s and $H don't immediately appear to be something that they're not.

And I forgot about horizontal space; on lines that get long it's not trivial.  That said, things like $select, $HOROLOG, and $piece carry a lot of meaning through the full word, and I think there's value in that.

Side note: for some reason I always want to use $g instead of $get (I don't, but it feels natural).  I think that's because, as a single syllable word which is a very simple operation, after seeing the definition for $get once, $g seems to fit it just as well as the full "get".

John Kumpf · Oct 31, 2019 go to post

Is there a reason it has to be an sql query?  Using object syntax, opening the object whose list you want to check, and then checking it with something like myObj.myListProperty is a very good option if possible.

John Kumpf · Dec 7, 2018 go to post

It appears that, for whatever reason, the System files are not automatically included for every class you write in Cache 2010.  When a class inherits from %RegisteredObject, you don't need to worry, because they'll be included via inheritance.  For other classes, like the one where I was seeing this issue, the system files are unavailable unless included by hand.

Adding Include.%occStatus to the top of the non-compiling classes fixes the issue for the $$$OK macro, although a better practice is to use Include.%occInclude, to get some other standard stuff with the status routines.