RInno 0.1.0: flexdashboards and the registry

[This article was first published on FI Labs - R Feed, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Today, we are happy to announce RInno’s first minor release. Over the past 5 months, RInno has been downloaded over 1200 times. Data scientists use it at the FDIC, the US Treasury and US Department of Agriculture among other organizations to install shiny apps on desktop computers. We have been getting weekly questions about its features and use cases, and we received our first question on StackOverflow. The most exciting development, however, is what led to today’s minor release.

Hanjo Odendaal, a PhD student and data science consultant from South Africa, came to us with a question about how to install RStudio’s newest dashboard package, flexdashboard. He had a fully formed idea about how it could work, so I asked him if he’d like to implement it together. Over the past few months, he has been an incredibly thoughtful contributor to the project, and he is the chief visionary for this release.

At the same time, many of the issues submitted on GitHub were related to app start up instability problems in various shared computing envirnments and non-standard desktop configurations. Therefore, this release includes stability improvements, which are a combination of registry queries and improvements to the Inno Setup Scripts produced by RInno. This information will allow Data Scientists to focus on their work instead of fixing broken apps. It also brings RInno one step closer to being enterprise ready. IT can do what they want, for the most part, and RInno should be able to handle what they throw at it.

Our next release will most likely include support for OAuth2 for advanced implementations that utilize RInno’s continuous installation methodology, but feel free add your thoughts in the comments section below or the issues section of GitHub. Each release will address community needs, so please tell us what you want RInno to support.

Newest Feature – flexdashboard support

# Create an example flexdashboard in the "app" directory
example_app(
	app_dir = "app", 
	type = "flexdashboard")
	
# Build an RInno installation framework w/ Pandoc
create_app(
	app_name = "myapp", 
	app_dir = "app", 
	include_Pandoc = TRUE) # See Pandoc Dependency section for details
	
# Compile the installer with Inno Setup
compile_iss()

If you are familiar with RInno, this is almost exactly the same as compiling a shiny app installer. Hanjo has abstracted away the differences between shiny apps and flexdashboards so that RInno supports flexdashboards seamlessly. There is no need to change your workflow or learn new arguments to use RInno with flexdashboards.

Flexdashboards

Dubbed “easy interactive dashboards for R” by RStudio, flexdashboards are a hybrid between shiny apps and rmarkdown documents. They provide the interactivity of a web application, with the statistical and data visualization power of R, managed via the simplicity of an rmarkdown document. Flexdashboards are an extremely powerful data science tool. At RStudio DevCon 2017, RStudio said that their long-term goal is to make flexdashboards stand-alone html, css and JavaScript apps without an R server. This would make it possible to open them in a browser without R running and expand their audience in many business settings. I am incredibly excited about this project, and we will continue to support it through RInno.

This NBA Scoring flexdashboard is a great example of the simplicity and power of these new applications. The dashboard includes a D3 JavaScript visualization and stat table, and it only requires 29 lines of rmarkdown code:

This is why Hanjo asked: “Can I use RInno with flexdashboards?” Well, thanks to him, now you can.

Flexdashboard Check

When create_app is called, RInno now runs Hanjo’s flexdashboard_check to determine if the app in app_dir is a flexdashboard. If it is, then flexdashboard and rmarkdown are added to the app’s R package dependency list, on config.cfg, and RInno prints a message to the R console to communicate its findings.

This flexdashboard will be used:
 - example.Rmd

Pandoc Dependency

As many of you probably know, rmarkdown uses a universal document conversion tool called Pandoc to convert rmarkdown documents into pdf, html and Microsoft Word documents among other formats. We decided to handle the Pandoc dependency like R itself. There are two arguments to manage it. The first, include_Pandoc, is a boolean argument that tells RInno to include Pandoc in the installation package. The second, Pandoc_version, is the version of Pandoc to include. It defaults to the version of Pandoc used by the rmarkdown package in order to ensure that it is the same version of Pandoc used to develop the dashboard, but that can be customized.

Similar to the way that RInno handles R, it only installs Pandoc if a computer does not have a registry entry for Pandoc. Once the installation is complete, RInno queries the registry for Pandoc’s installation path and uses it to kick off the flexdashboard. Keep in mind, this is not a version specific check because Pandoc does not have version information in its registry entry. As a result, one difference between the Pandoc check and the R check is that if users have a different version of Pandoc installed on their computers, this check will find it, regardless of its version, and not include the version of Pandoc included with the RInno installer. We may have to release some patches to handle this differently at some point in the near future.

The Registry

Over 100 lines of custom Delphi Pascal code (below) now query the registry and tailor installations to each machine. The helper functions determine if R and/or Pandoc are needed and modify the post installation step of Inno Setup to write a JSON file in each app’s utils folder called “regpaths.json” (the old-school programmer in you should appreciate seeing JSON created by Delphi Pascal, which predates JSON by a few decades). This file is accessed throughout the start up sequence to ensure that dependencies are easy to find.

As I mentioned earlier, there has been a decent amount of interest in using shared machines to install shiny apps using RInno. A few people have tested this new methodology on shared machines and it worked out of the box. So that is a promising sign if you are interested in doing something similar at your organization.

Pascal – code.iss

This section is for your inner geek. If you are not curious about how the registry query works, skip to the final thoughts section below.

First, I created constants for each of the easy registry entries (R, Chrome, Internet Explorer, and Firefox), and a variable for the registry JSON file.

[Code]
const
  RRegKey = 'Software\R-Core\R\{#RVersion}';
  ChromeRegKey = 'Software\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe';
  IERegKey = 'Software\Microsoft\Windows\CurrentVersion\App Paths\IEXPLORE.EXE';
  FFRegKey = 'Software\Microsoft\Windows\CurrentVersion\App Paths\firefox.exe';
var
  RegPathsFile: string;

I created a series of boolean helper functions which are called using the Check parameter of Inno Setup. This makes it possible to copy and run the R and Pandoc executables if the registry query is successful.

// Is R installed?
function RDetected(): boolean;
var
  success: boolean;
begin
  success := RegKeyExists(HKLM, RRegKey) or RegKeyExists(HKCU, RRegKey);
  begin
    Result := success;
  end;
end;

// If R is not detected, it is needed
function RNeeded(): Boolean;
begin
  Result := (RDetected = false);
end;

Pandoc’s registry entry is packed into the System PATH without the same level of specificity as the others. Therefore, detecting Pandoc is a bit more challenging. Pandoc’s registry entry also varies on every machine because it includes the user’s name. Luckily, Inno Setup has a constant called {localappdata}, which I expanded into the correct entry (PandocDir). After that, it is possible to use the Pascal string function Pos to determine if that unique string has been crammed into the system path.

// Pandoc is stored in the System PATH
function PandocDetected(): Boolean;
var
  PandocDir, Path: String;
begin
  Log('Checking for Pandoc in %PATH%');
  if RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', Path) then
  begin // Successfully read the value
    Log('HKCU\Environment\PATH = ' + Path);
    PandocDir := ExpandConstant('{localappdata}\Pandoc\');
    Log('Looking for Pandoc in %PATH%: ' + PandocDir + ' in ' + Path);
    if Pos(LowerCase(PandocDir), Lowercase(Path)) = 0 then
    begin
      Log('Did not find Pandoc in %PATH%');
      Result := False;
    end
    else
    begin
      Log('Found Pandoc in %PATH%');
      Result := True;
    end
  end
  else // The key probably doesn't exist
  begin
    Log('Could not access HKCU\Environment\PATH.');
    Result := False;
  end;
end;

// If Pandoc is not detected, it is needed
function PandocNeeded(): Boolean;
begin
  Result := (PandocDetected = false);
end;

JScript (run.js) does not like the single slashes created by Pascal, so I created this helper function to convert each of the installation paths into paths that JScript likes.

// Registry path update function (adds an extra backslash for json)
function AddBackSlash(Value: string): string;
begin
  Result := Value;
  StringChangeEx(Result, '\', '\\', True);
end;

Finally, CurStepChanged is a standard Inno Setup function that controls various steps of the installation process. I modified it to run some queries at the end of the installation and save the results to disk using JSON. The methodology is very similar for each program. If the registry query finds something, save it to the registry file. Otherwise, save “none” in its place.

// Save installation paths to JSON
procedure CurStepChanged(CurStep: TSetupStep);
var
  RPath, ChromePath, IEPath, FFPath, PandocPath: string;
begin
if CurStep = ssPostInstall then begin
    RPath := '';
    ChromePath := '';
    IEPath := '';
    FFPath := '';
		PandocPath := ExpandConstant('{localappdata}\Pandoc\');
    RegPathsFile := ExpandConstant('{app}\utils\regpaths.json');
    // Create registry paths file
    SaveStringToFile(RegPathsFile, '{' + #13#10, True);

    // R RegPath
    if RegQueryStringValue(HKLM, RRegKey, 'InstallPath', RPath) or RegQueryStringValue(HKCU, RRegKey, 'InstallPath', RPath) then
      SaveStringToFile(RegPathsFile, '"r": "' + AddBackSlash(RPath) + '",' + #13#10, True)
    else
      SaveStringToFile(RegPathsFile, '"r": "none",' + #13#10, True);

    // Chrome RegPath
    if RegQueryStringValue(HKLM, ChromeRegKey, 'Path', ChromePath) then
      SaveStringToFile(RegPathsFile, '"chrome": "' + AddBackSlash(ChromePath) + '",' + #13#10, True)
    else
      SaveStringToFile(RegPathsFile, '"chrome": "none",' + #13#10, True);

    // Internet Explorer RegPath
    if RegQueryStringValue(HKLM, IERegKey, '', IEPath) then
      SaveStringToFile(RegPathsFile, '"ie": "' + AddBackSlash(IEPath) + '",' + #13#10, True)
    else
      SaveStringToFile(RegPathsFile, '"ie": "none",' + #13#10, True);

    // Firefox RegPath
    if RegQueryStringValue(HKLM, FFRegKey, 'Path', FFPath) then
      SaveStringToFile(RegPathsFile, '"ff": "' + AddBackSlash(FFPath) + '",' + #13#10, True)
    else
      SaveStringToFile(RegPathsFile, '"ff": "none",' + #13#10, True);

    // Pandoc RegPath
    // ** Last Line in json file (no trailing comma) **
    if PandocDetected() then
      SaveStringToFile(RegPathsFile, '"pandoc": "' + AddBackSlash(PandocPath) + '"' + #13#10, True)
    else
      SaveStringToFile(RegPathsFile, '"pandoc": "none"' + #13#10, True);
  SaveStringToFile(RegPathsFile, '}', True);
  end;
end;

It was pretty exciting to get this working because these procedures are writing JSON to disk in a way that JScript can understand and start an R session reliably. Once R is running, app.R sets the browser option and tells R where Pandoc is installed. Previously, JScript was doing some guess-and-check to find R and R was doing some guess-and-check to find the browser. Now, the registry provides the information RInno needs. Otherwise, it fails gracefully using the “none” cases.

Final Thoughts

Along with improving the authentication of continuous installations, I am planning to use packrat to isolate RInno-installed apps from each user’s R package library. This, and other possible features, are in their nascent stages, but I am very excited to have Hanjo on the team. If you think you’re not qualified to contribute, just remember: badasses feel like imposters. I was a political science major who minored in economics and women and gender studies, and then I spent the first half of my career working in the non-profit industry doing project management and social work… I am not a computer scientist, and I only started doing real data science work in 2013. I could use your help.

To leave a comment for the author, please follow the link and comment on their blog: FI Labs - R Feed.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)