Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,13 +1,55 @@
# Generated by roxygen2: do not edit by hand

S3method(addsUtmParameters,connectClient)
S3method(addsUtmParameters,connectCloudClient)
S3method(addsUtmParameters,shinyAppsClient)
S3method(as.data.frame,rsconnect_secret)
S3method(format,rsconnect_secret)
S3method(print,linterResults)
S3method(print,rsconnect_secret)
S3method(pythonEnabledByDefault,connectClient)
S3method(pythonEnabledByDefault,connectCloudClient)
S3method(pythonEnabledByDefault,shinyAppsClient)
S3method(redactsUserEmails,connectClient)
S3method(redactsUserEmails,connectCloudClient)
S3method(redactsUserEmails,shinyAppsClient)
S3method(requiresUpload,connectClient)
S3method(requiresUpload,connectCloudClient)
S3method(requiresUpload,shinyAppsClient)
S3method(serverDisplayName,connectClient)
S3method(serverDisplayName,connectCloudClient)
S3method(serverDisplayName,shinyAppsClient)
S3method(staticRmdNeedsShiny,connectClient)
S3method(staticRmdNeedsShiny,connectCloudClient)
S3method(staticRmdNeedsShiny,shinyAppsClient)
S3method(str,rsconnect_secret)
S3method(supportsEnvVarManagement,connectClient)
S3method(supportsEnvVarManagement,connectCloudClient)
S3method(supportsEnvVarManagement,shinyAppsClient)
S3method(supportsEnvVars,connectClient)
S3method(supportsEnvVars,connectCloudClient)
S3method(supportsEnvVars,shinyAppsClient)
S3method(supportsMetadataSync,connectClient)
S3method(supportsMetadataSync,connectCloudClient)
S3method(supportsMetadataSync,shinyAppsClient)
S3method(supportsNodejs,connectClient)
S3method(supportsNodejs,connectCloudClient)
S3method(supportsNodejs,shinyAppsClient)
S3method(supportsOptionalInviteEmail,connectClient)
S3method(supportsOptionalInviteEmail,connectCloudClient)
S3method(supportsOptionalInviteEmail,shinyAppsClient)
S3method(supportsUserManagement,connectClient)
S3method(supportsUserManagement,connectCloudClient)
S3method(supportsUserManagement,shinyAppsClient)
S3method(supportsVisibility,connectClient)
S3method(supportsVisibility,connectCloudClient)
S3method(supportsVisibility,shinyAppsClient)
S3method(uploadBundle,connectClient)
S3method(uploadBundle,connectCloudClient)
S3method(uploadBundle,shinyAppsClient)
S3method(usesPasswordFile,connectClient)
S3method(usesPasswordFile,connectCloudClient)
S3method(usesPasswordFile,shinyAppsClient)
export(accountInfo)
export(accountUsage)
export(accounts)
Expand Down
14 changes: 7 additions & 7 deletions R/appMetadata.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ appMetadata <- function(
quarto = NA,
appMode = NULL,
contentCategory = NULL,
isShinyappsServer = FALSE,
staticRmdNeedsShiny = FALSE,
metadata = list()
) {
check_bool(quarto, allow_na = TRUE)
Expand Down Expand Up @@ -35,7 +35,7 @@ appMetadata <- function(
appDir,
appFiles,
usesQuarto = quarto,
isShinyappsServer = isShinyappsServer
staticRmdNeedsShiny = staticRmdNeedsShiny
)
appMode <- appModeResult$appMode
inferredPrimaryFile <- appModeResult$primaryFile
Expand All @@ -50,7 +50,7 @@ appMetadata <- function(
appDir,
appFiles,
usesQuarto = quarto,
isShinyappsServer = isShinyappsServer
staticRmdNeedsShiny = staticRmdNeedsShiny
)$primaryFile
}

Expand Down Expand Up @@ -136,7 +136,7 @@ inferAppMode <- function(
appDir,
appFiles,
usesQuarto = NA,
isShinyappsServer = FALSE
staticRmdNeedsShiny = FALSE
) {
rootFiles <- appFiles[dirname(appFiles) == "."]
absoluteRootFiles <- file.path(appDir, rootFiles)
Expand Down Expand Up @@ -236,9 +236,9 @@ inferAppMode <- function(
primaryFile = basename(primaryDocFile)
))
} else {
# For shinyapps.io, treat "rmd-static" app mode as "rmd-shiny" so that
# it can be served from a shiny process in Connect
if (isShinyappsServer) {
# Some servers can only serve R Markdown from a Shiny process, so they
# get "rmd-shiny" in place of "rmd-static".
if (staticRmdNeedsShiny) {
return(list(
appMode = "rmd-shiny",
primaryFile = basename(primaryDocFile)
Expand Down
7 changes: 4 additions & 3 deletions R/applications.R
Original file line number Diff line number Diff line change
Expand Up @@ -403,15 +403,16 @@ syncAppMetadata <- function(appPath = ".") {
for (i in seq_len(nrow(deploys))) {
curDeploy <- deploys[i, ]

# don't sync if published to RPubs or Connect Cloud
# RPubs has no client, so check it before the client is created

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

future: Is it a mistake that rpubs has no client? Probably not worth addressing given that publishing stops next year.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rpubs behaves pretty differently to the other servers since we don't track user accounts for it. agree it's not worth addressing now, most of the S3 methods would be unused anyway. shouldn't be too hard to remove the small amount of rpubs-specific code when publishing is no longer supported.

if (isRPubs(curDeploy$server)) {
next
} else if (isPositConnectCloudServer(curDeploy$server)) {
next
}

account <- accountInfo(curDeploy$account, curDeploy$server)
client <- clientForAccount(account)
if (!supportsMetadataSync(client)) {
next
}

application <- tryCatch(
client$getApplication(curDeploy$appId),
Expand Down
64 changes: 28 additions & 36 deletions R/auth.R
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ cleanupPasswordFile <- function(appDir) {
invisible(TRUE)
}

checkSupportsUserManagement <- function(client, call = caller_env()) {
if (!supportsUserManagement(client)) {
cli::cli_abort(
"rsconnect can't manage application users on {serverDisplayName(client)}.",
call = call
)
}
}

# Internal: resolve the target content for collaborator management functions.
# On PCC, an explicit contentId targets the content directly; otherwise reads
# the local deployment record to get the content id (appId) rather than matching
Expand Down Expand Up @@ -214,9 +223,8 @@ addAuthorizedUser <- function(
emailMessage = NULL
) {
accountDetails <- accountInfo(account, server)
if (!isPositConnectCloudServer(accountDetails$server)) {
checkShinyappsServer(accountDetails$server)
}
api <- clientForAccount(accountDetails)
checkSupportsUserManagement(api)

application <- resolveContentTarget(
accountDetails,
Expand All @@ -225,23 +233,18 @@ addAuthorizedUser <- function(
contentId
)

# check for and remove password file (shinyapps.io only; PCC has no password file)
if (!isPositConnectCloudServer(accountDetails$server)) {
if (usesPasswordFile(api)) {
cleanupPasswordFile(appDir)
}

# PCC always emails invitees; warn only when caller explicitly opts out
if (
isPositConnectCloudServer(accountDetails$server) &&
identical(sendEmail, FALSE)
) {
# Warn only when the caller explicitly opts out of the email.
if (!supportsOptionalInviteEmail(api) && identical(sendEmail, FALSE)) {
cli::cli_warn(
"{.arg sendEmail} is ignored on Posit Connect Cloud; PCC always sends an invitation email."
"{.arg sendEmail} is ignored on {serverDisplayName(api)}, which always sends an invitation email."
)
}

# fetch authorization list
api <- clientForAccount(accountDetails)
api$inviteApplicationUser(
application$id,
validateEmail(email),
Expand Down Expand Up @@ -290,9 +293,8 @@ removeAuthorizedUser <- function(
server = NULL
) {
accountDetails <- accountInfo(account, server)
if (!isPositConnectCloudServer(accountDetails$server)) {
checkShinyappsServer(accountDetails$server)
}
api <- clientForAccount(accountDetails)
checkSupportsUserManagement(api)

application <- resolveContentTarget(
accountDetails,
Expand All @@ -301,16 +303,14 @@ removeAuthorizedUser <- function(
contentId
)

# check and remove password file (shinyapps.io only; PCC has no password file)
if (!isPositConnectCloudServer(accountDetails$server)) {
if (usesPasswordFile(api)) {
cleanupPasswordFile(appDir)
}

# resolve content exactly once: use impl so showUsers() does not call
# resolveContentTarget() a second time (a second interactive prompt could
# return a different record, causing removeApplicationUser to act on the
# wrong content).
api <- clientForAccount(accountDetails)
users <- showUsers_impl(
api,
application$id,
Expand All @@ -325,15 +325,13 @@ removeAuthorizedUser <- function(
} else if (user %in% users$email) {
user <- users[which(users$email == user), ]
} else {
# Only PCC redacts emails, and the hint only helps someone who searched by
# email (an id-based lookup already avoids the problem).
redactionHint <-
isPositConnectCloudServer(accountDetails$server) &&
grepl("@", user, fixed = TRUE)
# The hint only helps someone who searched by email. A lookup by id is not
# affected by redaction.
redactionHint <- redactsUserEmails(api) && grepl("@", user, fixed = TRUE)
cli::cli_abort(c(
"User {.val {user}} not found.",
i = if (redactionHint) {
"On Posit Connect Cloud an email can be redacted and won't match; pass the user id from {.fn showUsers} instead."
"On {serverDisplayName(api)} an email can be redacted and won't match; pass the user id from {.fn showUsers} instead."
}
))
}
Expand Down Expand Up @@ -393,9 +391,8 @@ showUsers <- function(
server = NULL
) {
accountDetails <- accountInfo(account, server)
if (!isPositConnectCloudServer(accountDetails$server)) {
checkShinyappsServer(accountDetails$server)
}
api <- clientForAccount(accountDetails)
checkSupportsUserManagement(api)

application <- resolveContentTarget(
accountDetails,
Expand All @@ -404,7 +401,6 @@ showUsers <- function(
contentId
)

api <- clientForAccount(accountDetails)
showUsers_impl(
api,
application$id,
Expand Down Expand Up @@ -449,9 +445,8 @@ showInvited <- function(
server = NULL
) {
accountDetails <- accountInfo(account, server)
if (!isPositConnectCloudServer(accountDetails$server)) {
checkShinyappsServer(accountDetails$server)
}
api <- clientForAccount(accountDetails)
checkSupportsUserManagement(api)

application <- resolveContentTarget(
accountDetails,
Expand All @@ -460,7 +455,6 @@ showInvited <- function(
contentId
)

api <- clientForAccount(accountDetails)
showInvited_impl(api, application$id)
}

Expand Down Expand Up @@ -505,9 +499,8 @@ resendInvitation <- function(
server = NULL
) {
accountDetails <- accountInfo(account, server)
if (!isPositConnectCloudServer(accountDetails$server)) {
checkShinyappsServer(accountDetails$server)
}
api <- clientForAccount(accountDetails)
checkSupportsUserManagement(api)

# resolve content exactly once, then fetch invitations via impl (avoids a
# second resolveContentTarget() call).
Expand All @@ -517,7 +510,6 @@ resendInvitation <- function(
appName,
contentId
)
api <- clientForAccount(accountDetails)
invited <- showInvited_impl(api, application$id)

invite <- as.character(invite)
Expand Down
9 changes: 5 additions & 4 deletions R/bundlePython.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ pythonConfigurator <- function(python, forceGenerate = FALSE) {
}
}

# python is enabled on Connect, but not on Shinyapps
getPythonForTarget <- function(path, accountDetails) {
targetIsShinyapps <- isShinyappsServer(accountDetails$server)
pythonEnabled <- getOption("rsconnect.python.enabled", !targetIsShinyapps)
getPythonForTarget <- function(path, client) {
pythonEnabled <- getOption(
"rsconnect.python.enabled",
pythonEnabledByDefault(client)
)
if (pythonEnabled) {
getPython(path)
} else {
Expand Down
Loading
Loading