Skip to content

Commit

Permalink
Initializing gh-pages branch
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubnowicki committed May 10, 2024
0 parents commit 66ce2f0
Show file tree
Hide file tree
Showing 46 changed files with 3,887 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .Rprofile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
if (file.exists("renv")) {
source("renv/activate.R")
} else {
# The `renv` directory is automatically skipped when deploying with rsconnect.
message("No 'renv' directory found; renv won't be activated.")
}

# Allow absolute module imports (relative to the app root).
options(box.path = getwd())
33 changes: 33 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
on:
workflow_dispatch:
push:
branches: add-webpage

name: Quarto Publish

defaults:
run:
working-directory: ./docs

jobs:
build-deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Copy README
run: cp ../README.md .

- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2

- name: Render and Publish
uses: quarto-dev/quarto-actions/publish@v2
with:
target: gh-pages
path: docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 changes: 72 additions & 0 deletions .github/workflows/rhino-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Rhino Test
on: push
permissions:
contents: read
jobs:
main:
name: Run linters and tests
runs-on: ubuntu-22.04
steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Setup system dependencies
run: |
packages=(
# List each package on a separate line.
)
sudo apt-get update
sudo apt-get install --yes "${packages[@]}"
- name: Setup R
uses: r-lib/actions/setup-r@v2
with:
r-version: renv

- name: Setup R dependencies
uses: r-lib/actions/setup-renv@v2

- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 20

- name: Lint R
if: always()
shell: Rscript {0}
run: rhino::lint_r()

- name: Lint JavaScript
if: always()
shell: Rscript {0}
run: rhino::lint_js()

- name: Lint Sass
if: always()
shell: Rscript {0}
run: rhino::lint_sass()

- name: Build JavaScript
if: always()
shell: Rscript {0}
run: rhino::build_js()

- name: Build Sass
if: always()
shell: Rscript {0}
run: rhino::build_sass()

- name: Run R unit tests
if: always()
shell: Rscript {0}
run: rhino::test_r()

- name: Run Cypress end-to-end tests
if: always()
uses: cypress-io/github-action@v6
with:
working-directory: .rhino # Created by earlier commands which use Node.js
start: npm run run-app
project: ../tests
wait-on: 'http://localhost:3333/'
wait-on-timeout: 60
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.Rproj.user
.Rhistory
.RData
.Ruserdata
.DS_Store
.Renviron
5 changes: 5 additions & 0 deletions .lintr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
linters:
linters_with_defaults(
line_length_linter = line_length_linter(100),
object_usage_linter = NULL # Does not work with `box::use()`.
)
3 changes: 3 additions & 0 deletions .renvignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Only use `dependencies.R` to infer project dependencies.
*
!dependencies.R
7 changes: 7 additions & 0 deletions .rscignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.github
.lintr
.renvignore
.Renviron
.rhino
.rscignore
tests
9 changes: 9 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Copyright (C) 2024 Appsilon and Elkem

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of Appsilon or Elkem shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Appsilon and Elkem.
16 changes: 16 additions & 0 deletions LogAnalyzer.Rproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Version: 1.0

RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default

EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8

RnwWeave: Sweave
LaTeX: pdfLaTeX

AutoAppendNewline: Yes
StripTrailingWhitespace: Yes
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# LogAnalyzer

The LogAnalyzer open-source app is a simple, plug and play application developed first in collaboration with [Elkem](https://www.elkem.com/). The app provides an ability to get semantically coloured logs for applications deployed on Posit Connect by simply changing the default environment variables and deploying it on Posit Connect. Given the general usefulness of the app, we have decided to share it with the wider community to use and improve upon it. No more sifting through long text files; you can simply find the reds and see where things break. It has never been easier to investigate what goes wrong with your applications.

# How it works?

- The LogAnalyzer app uses the Posit Connect API to fetch logs for application run jobs, semantically colouring them so they are easier to read.
- The only thing you need to set this up and use is the `CONNECT_API_KEY` set as an environment variable. The idea is that the key should come from either an admin account or someone with privileges to view all apps. Apps that are not available to a user will not have their logs available to them.
- If you want to test the app locally, you will need to set the `CONNECT_SERVER` as an environment variable. When deployed, the `CONNECT_SERVER` is setup automatically for you.

![LogAnalyzerDemo](https://github.com/Appsilon/LogAnalyzer/assets/26517718/90d1111d-006b-42db-8d8b-b55ee391cb21)

# Credits
It was our collaboration with <img src="https://github.com/Appsilon/LogAnalyzer/assets/26517718/b26fd60b-4989-4d31-a01e-5cf865f5ec9b" alt="Elkem" width="50"/> which led to the creation of this app. The initial idea came from use-cases where we realised we wanted to track all the logs and be able to read them properly since Posit Connect was the de facto deployment environment. When we made this app, we realised there was potential in sharing this with the rest of the community and invite everyone to use it and add it. We appreciate and thank Elkem for their openness to share it with the world.

You can read more about Appsilon and Elkem's collaboration on our case study [here](https://www.appsilon.com/case-studies/refining-elkems-processes-with-advanced-data-analytics).

## Appsilon

<img src="https://avatars0.githubusercontent.com/u/6096772" align="right" alt="" width="6%" />

Appsilon is a **Posit (formerly RStudio) Full Service Certified Partner**.<br/>
Learn more at [appsilon.com](https://appsilon.com).

Get in touch [[email protected]](mailto:[email protected])

Explore the [Rhinoverse](https://rhinoverse.dev) - a family of R packages built around [Rhino](https://appsilon.github.io/rhino/)!

<a href = "https://appsilon.us16.list-manage.com/subscribe?u=c042d7c0dbf57c5c6f8b54598&id=870d5bfc05" target="_blank">
<img src="https://raw.githubusercontent.com/Appsilon/website-cdn/gh-pages/shiny_weekly_light.jpg" alt="Subscribe for Shiny tutorials, exclusive articles, R/Shiny community events, and more."/>
</a>
2 changes: 2 additions & 0 deletions app.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Rhino / shinyApp entrypoint. Do not edit.
rhino::app()
Empty file added app/js/index.js
Empty file.
184 changes: 184 additions & 0 deletions app/logic/api_utils.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
box::use(
dplyr[filter],
glue[glue],
httr2[
req_auth_bearer_token,
req_dry_run,
req_perform,
req_user_agent,
request,
resp_body_string
],
jsonlite[fromJSON],
magrittr[`%>%`],
)

#' Simple function to get the access token from environment
#' @return Character token, if present
get_access_token <- function() {
token <- Sys.getenv(
"CONNECT_API_KEY",
unset = "NO_TOKEN"
)
if (token == "NO_TOKEN" || token == "") {
stop("Are you sure your CONNECT_API_KEY is set?")
} else {
token
}
}

#' Simple function to make an API url
#' @param host Character. Default CONNECT_SERVER set as an envvar
#' @param endpoint Character. Default is "content"
#' @param version Logical. Whether to use versioned API. Default is "v1"
#' @return url for the API
get_api_url <- function(
host = Sys.getenv("CONNECT_SERVER"),
endpoint = "content",
version = "v1"
) {
glue("{host}__api__/{version}/{endpoint}/")
}

#' Function to get a list of all apps belonging to the token
#'
#' @param app_mode_filter Character. The filter for app_mode in the API
#' response. Default is "shiny".
#' @param endpoint Character. Default is "content"
#' @param dry_run Logical. Whether to dry run the API for debugging.
#' Default is FALSE
#' @export
get_app_list <- function(
app_mode_filter = "shiny",
endpoint = "content",
dry_run = FALSE
) {

url <- get_api_url(
endpoint = endpoint
)

api_request <- request(url) %>%
req_user_agent("LogAnalyzer") %>%
req_auth_bearer_token(get_access_token())

if (dry_run) {
api_request %>%
req_dry_run()
} else {
api_request %>%
req_perform() %>%
resp_body_string() %>%
fromJSON() %>%
filter(app_mode == app_mode_filter)
}
}

#' Function to get a list of all jobs for a specific app
#'
#' @param guid Character. The guid for the app in question
#' @param endpoint Character. Default is "content"
#' @param dry_run Logical. Whether to dry run the API for debugging.
#' Default is FALSE
#' @export
get_job_list <- function(
guid = NULL,
endpoint = "content",
dry_run = FALSE
) {

url <- get_api_url(
endpoint = endpoint
)

api_request <- request(
glue("{url}{guid}/jobs")
) %>%
req_user_agent("LogAnalyzer") %>%
req_auth_bearer_token(get_access_token())

if (dry_run) {
api_request %>%
req_dry_run()
} else {
api_request %>%
req_perform() %>%
resp_body_string() %>%
fromJSON()
}
}

#' Function to get a list of all logs for a job for a specific app
#'
#' @param guid Character. The guid for the app in question
#' @param job_key Character. The key for the job in question
#' @param endpoint Character. Default is "content"
#' @param tail Logical. Whether to show the tail only for the logs
#' @param dry_run Logical. Whether to dry run the API for debugging.
#' Default is FALSE
#' @export
get_job_logs <- function(
guid = NULL,
job_key = NULL,
endpoint = "content",
tail = FALSE,
dry_run = FALSE
) {

url <- get_api_url(
endpoint = endpoint
)

api_request <- request(
glue("{url}{guid}/jobs/{job_key}/{ifelse(tail, 'tail', 'log')}")
) %>%
req_user_agent("LogAnalyzer") %>%
req_auth_bearer_token(get_access_token())

if (dry_run) {
api_request %>%
req_dry_run()
} else {
logs <- api_request %>%
req_perform() %>%
resp_body_string() %>%
fromJSON()
logs["entries"] %>%
data.frame()
}
}

#' Function to download the logfile for a specific app
#'
#' @param guid Character. The guid for the app in question
#' @param job_key Character. The key for the job in quesrtion
#' @param endpoint Character. Default is "content"
#' @param dry_run Logical. Whether to dry run the API for debugging.
#' Default is FALSE
#' @export
download_job_logs <- function(
guid = NULL,
job_key = NULL,
endpoint = "content",
dry_run = FALSE
) {

url <- get_api_url(
endpoint = endpoint
)

api_request <- request(
glue("{url}{guid}/jobs/{job_key}/download")
) %>%
req_user_agent("LogAnalyzer") %>%
req_auth_bearer_token(get_access_token())

if (dry_run) {
api_request %>%
req_dry_run()
} else {
api_request %>%
req_perform() %>%
resp_body_string()
}
}
Loading

0 comments on commit 66ce2f0

Please sign in to comment.