Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add web app #12

Merged
merged 78 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
4b43475
chore: scaffold web app
ivan-aksamentov Feb 16, 2023
2fca76c
feat: convert data and display per-variant charts
ivan-aksamentov Feb 17, 2023
0f6d85e
feat: also plot regions, not only countries
ivan-aksamentov Feb 17, 2023
e64b908
fix: nest pathogen names under /pathogen url to allow for /faq
ivan-aksamentov Feb 17, 2023
37efece
feat: also display ranges
ivan-aksamentov Feb 17, 2023
5021f7f
fix: remove extra slash
ivan-aksamentov Feb 17, 2023
12d1bab
fix: missing name in area plots
ivan-aksamentov Feb 17, 2023
b58e5d7
fix: avg values in the tooltips
ivan-aksamentov Feb 17, 2023
71c2b31
fix: sorting in the tooltip
ivan-aksamentov Feb 17, 2023
138e4e6
refactor: move io function to the io module
ivan-aksamentov Feb 17, 2023
876c609
feat: display per-region plots
ivan-aksamentov Feb 17, 2023
4ebb125
feat: show color boxes in tooltips instead of lines
ivan-aksamentov Feb 17, 2023
0c3c247
refactor: lint
ivan-aksamentov Feb 17, 2023
24e0b88
fix: colors of continents
ivan-aksamentov Feb 17, 2023
90802f3
feat: add sidebar with geo selection on variants page
ivan-aksamentov Feb 18, 2023
f34e762
feat: add sidebar with variant selection on regions page
ivan-aksamentov Feb 19, 2023
28ed59f
feat: add sidebar filtering
ivan-aksamentov Feb 19, 2023
1dfd161
chore: add data to the public directory
ivan-aksamentov Feb 19, 2023
dfb3b55
fix: workaround for broken home page links
ivan-aksamentov Feb 19, 2023
86b145a
feat: setup internationalization
ivan-aksamentov Feb 19, 2023
3bc9a50
feat: search internationalized names
ivan-aksamentov Feb 19, 2023
a5b8902
chore: fix vercel rewrites
ivan-aksamentov Feb 19, 2023
3cab0a6
feat: search in transliterated names
ivan-aksamentov Feb 19, 2023
733b809
refactor: lint
ivan-aksamentov Feb 19, 2023
9e9eac6
feat: adjust web convert script to use the flat csv inputs
ivan-aksamentov Feb 20, 2023
ef7732e
chore: update data
ivan-aksamentov Feb 20, 2023
31edb63
refactor: upgrade to next.js 13; use ts-node
ivan-aksamentov Feb 21, 2023
9993959
chore: upgrade dependencies
ivan-aksamentov Feb 21, 2023
fac8e1d
feat: add page progress bar
ivan-aksamentov Feb 21, 2023
2ad4a7f
chore: cleanup config
ivan-aksamentov Feb 21, 2023
ab61266
fix: links to plot pages
ivan-aksamentov Feb 21, 2023
dd718c4
fix: extra suspense
ivan-aksamentov Feb 21, 2023
9e67971
chore: speedup linting in dev mode
ivan-aksamentov Feb 22, 2023
5235f55
feat: add virus selection on home page
ivan-aksamentov Feb 22, 2023
200d7ff
feat: add region and variant selectors on pathogen page
ivan-aksamentov Feb 22, 2023
a8f443e
feat: add navigation breadcrumb
ivan-aksamentov Feb 22, 2023
b50c9ba
feat: show only one plot on variant/region pages
ivan-aksamentov Feb 22, 2023
3897ff9
feat: update translations
ivan-aksamentov Feb 22, 2023
db3a607
chore: add input data
ivan-aksamentov Feb 22, 2023
0b23d16
fix: layout problems
ivan-aksamentov Feb 22, 2023
d787374
chore: enable legacy browser support
ivan-aksamentov Feb 22, 2023
cb292af
feat: add counts and totals
ivan-aksamentov Feb 22, 2023
fb6b5c4
feat: remove extra title in the tooltip
ivan-aksamentov Feb 22, 2023
3381a76
chore: fix next.js config props
ivan-aksamentov Feb 22, 2023
6a0a5e7
refactor: memoize tooltip components
ivan-aksamentov Feb 22, 2023
65f0904
feat: add date range sliders
ivan-aksamentov Feb 22, 2023
8b9737f
fix: display friendly pathogen name in breadcrumb
ivan-aksamentov Feb 23, 2023
3ebf16c
fix: typo
ivan-aksamentov Feb 23, 2023
5ee3c2f
fix: correctly restrict chart x domain when filtering by date
ivan-aksamentov Feb 23, 2023
7df1af6
feat: style date slider consistently with the app theme
ivan-aksamentov Feb 23, 2023
3dcb06b
fix: align breadcrumb arrows
ivan-aksamentov Feb 23, 2023
09b1b34
fix: ensure unique react keys
ivan-aksamentov Feb 23, 2023
617b3ae
feat: add 'select all' checkbox in the sidebar
ivan-aksamentov Feb 23, 2023
bd42931
fix: prevent sidebar overflow
ivan-aksamentov Feb 23, 2023
d076748
fix: sidebar header width
ivan-aksamentov Feb 23, 2023
6737945
feat: select only regions by default
ivan-aksamentov Feb 24, 2023
ec0ba37
feat: increase tick label font size
ivan-aksamentov Feb 24, 2023
f2c5da0
feat: increase margins of the plot
ivan-aksamentov Feb 24, 2023
31a03fb
feat: increase plot line width
ivan-aksamentov Feb 24, 2023
f8a5d2d
feat: reduce number of significant digits in the tool tip from 3 to 2
ivan-aksamentov Feb 24, 2023
b49f437
refactor: lint
ivan-aksamentov Feb 24, 2023
239947b
feat: show 4-digit years on time axes
ivan-aksamentov Feb 24, 2023
d1ed9c2
fix: ensure language switcher fits to the screen
ivan-aksamentov Feb 24, 2023
1569468
feat: ensure correct number formatting in different locales
ivan-aksamentov Feb 24, 2023
fc84f81
feat: prettify previous/next nav buttons
ivan-aksamentov Feb 24, 2023
16ab526
feat: add brand name
ivan-aksamentov Feb 24, 2023
728fdd0
feat: small theme adjustments
rneher Feb 24, 2023
309cb5a
Create CONTRIBUTING.md
ArtPoon Apr 28, 2023
184d862
Update CONTRIBUTING.md
ArtPoon Apr 28, 2023
f9991f0
Update CONTRIBUTING.md
ArtPoon Apr 28, 2023
6efd374
Update CONTRIBUTING.md
ArtPoon Apr 28, 2023
30fcc7e
Update CONTRIBUTING.md
ArtPoon Apr 28, 2023
5058086
Merge pull request #5 from ArtPoon/web
ivan-aksamentov Apr 28, 2023
fd53628
updated contributing doc with details from @ivan-aksamentov
ArtPoon May 5, 2023
9b47aaf
Merge branch 'web' of https://github.com/ArtPoon/flu_frequencies into…
ArtPoon May 5, 2023
99ed79e
Merge pull request #10 from ArtPoon/web
ivan-aksamentov May 5, 2023
d899551
Merge remote-tracking branch 'origin/master' into web
ivan-aksamentov May 11, 2023
5b77080
chore: rename data dir to avoid clashes
ivan-aksamentov May 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*
!/\.nvmrc
!/requirements.txt
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ __pycache__/

/.snakemake/
/.vscode/
.DS_Store
.DS_Store
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
18.14.0
16 changes: 16 additions & 0 deletions .vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.build
.cache
.github
.ignore
.reports
/acknowledgements
/cluster_tables
/compare_lineages
/country_case_data
/figures
/other_figures
/overall_trends_figures
/plasmid_data
/travel_data
docs
node_modules
51 changes: 51 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
This document provides some instructions for setting up a development environment for working on the front end of this module. These steps assume that you are on a Debian/Ubuntu system (I was running Ubuntu 20.04) without any additional Node-related packages installed. It should not require super-user privileges.

# Installation
1. Clone this repository and enter the package root with `cd flu_frequencies/web`.
2. The version of NodeJS provided with Ubuntu 20 is too old (v10.19.0). To install a newer version that co-exists with the system version, I used [Node Version Manager](https://github.com/nvm-sh/nvm). Use one of the [installation script](https://github.com/nvm-sh/nvm#install--update-script) provided by the developers. This creates a hidden directory `.nvm` under `$HOME`.
```console
art@Kestrel:~/git/flu_frequencies/web$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 15916 100 15916 0 0 353k 0 --:--:-- --:--:-- --:--:-- 353k
=> Downloading nvm from git to '/home/art/.nvm'
=> Cloning into '/home/art/.nvm'...
remote: Enumerating objects: 359, done.
remote: Counting objects: 100% (359/359), done.
remote: Compressing objects: 100% (305/305), done.
remote: Total 359 (delta 40), reused 168 (delta 28), pack-reused 0
Receiving objects: 100% (359/359), 219.46 KiB | 13.72 MiB/s, done.
Resolving deltas: 100% (40/40), done.
* (HEAD detached at FETCH_HEAD)
master
=> Compressing and cleaning up git repository

=> nvm source string already in /home/art/.bashrc
=> bash_completion source string already in /home/art/.bashrc
=> Close and reopen your terminal to start using nvm or run the following to use it now:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
```
3. Open a new terminal window so that `$PATH` binary search path includes the `.nvm` directory.
4. We want to install the Node.js version specified in the hidden `.nvmrc` file. (Currently the version number is `18.14.0`.) To automatically install the required version, run `nvm install` and `nvm use`:
```console
Downloading and installing node v18.14.0...
Downloading https://nodejs.org/dist/v18.14.0/node-v18.14.0-linux-x64.tar.xz...
###################################################################################### 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v18.14.0 (npm v9.3.1)
Creating default alias: default -> 18.14.0 (-> v18.14.0)
```
You can confirm that you are running this new version with the following command:
```console
art@Kestrel:~/git/flu_frequencies/web$ which node
/home/art/.nvm/versions/node/v18.14.0/bin/node
```
5. The above step also installs the [Node Package Manager](https://www.npmjs.com/) `npm` in the same local directory. Install the package dependencies by running `npm install` (if the package manager complains about conflicting dependencies, use a `-force` flag).
6. Install the [Yarn](https://yarnpkg.com/) package manager: `npm install --global yarn`
7. Copy the environment variables with `cp .env.example .env`.
8. Run `yarn install` and `yarn dev` to start the server.
9. Navigate to `localhost:3000` in your web browser.
21,081 changes: 21,081 additions & 0 deletions data_web/inputs/flu-h3n2.csv

Large diffs are not rendered by default.

Binary file added data_web/inputs/images/flu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data_web/inputs/images/mpox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data_web/inputs/images/rsv.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added data_web/inputs/images/sars-cov-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions data_web/inputs/pathogens.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[
{
"name": "flu-h3n2",
"nameFriendly": "Influenza H3N2",
"image": {
"file": "images/flu.png",
"credit": "Centers for Disease Control and Prevention, National Center for Immunization and Respiratory Diseases (NCIRD)",
"source": "https://www.cdc.gov/flu/resource-center/freeresources/graphics/images.htm"
},
"isEnabled": true,
"isVisible": true
},
{
"name": "flu-h1n1pdm",
"nameFriendly": "Influenza H1N1pdm",
"image": {
"file": "images/flu.png",
"credit": "Centers for Disease Control and Prevention, National Center for Immunization and Respiratory Diseases (NCIRD)",
"source": "https://www.cdc.gov/flu/resource-center/freeresources/graphics/images.htm"
},
"isEnabled": false,
"isVisible": true
},
{
"name": "sars-cov-2",
"nameFriendly": "SARS-CoV-2",
"image": {
"file": "images/sars-cov-2.png",
"credit": "Centers for Disease Control and Prevention Newsroom, Image Library",
"source": "https://www.cdc.gov/media/subtopic/images.htm"
},
"isEnabled": false,
"isVisible": true
},
{
"name": "monkeypox",
"nameFriendly": "Monkeypox",
"image": {
"file": "images/mpox.png",
"credit": "Centers for Disease Control and Prevention Newsroom, Image Library",
"source": "https://www.cdc.gov/media/subtopic/images.htm"
},
"isEnabled": false,
"isVisible": true
},
{
"name": "rsv",
"nameFriendly": "Respiratory Syncytial Virus (RSV)",
"image": {
"file": "images/rsv.png",
"credit": "Centers for Disease Control and Prevention Newsroom, Public Health Image Library (PHIL)",
"source": "https://phil.cdc.gov/Details.aspx?pid=2175"
},
"isEnabled": false,
"isVisible": true
}
]
41 changes: 41 additions & 0 deletions docker-dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash

set -euo pipefail

export NICE="nice -14 ionice -c2 -n3"

DOCKER_REPO="neherlab/flu_frequencies"
DOCKER_IMAGE_NAME_SAFE="${DOCKER_REPO//\//-}"
DOCKER_CONTAINER_NAME="${DOCKER_IMAGE_NAME_SAFE}-$(date +%s)"

USER="user"
GROUP="group"

DEFAULT_COMMAND="cd web && yarn install && yarn dev"

${NICE} docker build -q \
--file="docker/docker-dev.dockerfile" \
--tag="${DOCKER_REPO}" \
--network=host \
--build-arg="UID=$(id -u)" \
--build-arg="GID=$(id -g)" \
--build-arg="USER=${USER}" \
--build-arg="GROUP=${GROUP}" \
"$(pwd)" >/dev/null

${NICE} docker run -it --rm \
--network=host \
--init \
--name="${DOCKER_CONTAINER_NAME}" \
--hostname="${DOCKER_IMAGE_NAME_SAFE}" \
--user="$(id -u):$(id -g)" \
--volume="$(pwd):/workdir" \
--workdir="/workdir" \
--env="UID=$(id -u)" \
--env="GID=$(id -g)" \
--env="USER=${USER}" \
--env="GROUP=${GROUP}" \
--env="PS1=\${USER}@\${HOST}" \
--ulimit core=0 \
"${DOCKER_REPO}" \
bash -c "set -euo pipefail; ${*:-${DEFAULT_COMMAND}}"
97 changes: 97 additions & 0 deletions docker/docker-dev.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Freeze base image version to
# ubuntu:20.04 (pushed 2022-06-06T22:39:15.718739Z)
# https://hub.docker.com/layers/ubuntu/library/ubuntu/20.04/images/sha256-b2339eee806d44d6a8adc0a790f824fb71f03366dd754d400316ae5a7e3ece3e
FROM ubuntu@sha256:b2339eee806d44d6a8adc0a790f824fb71f03366dd754d400316ae5a7e3ece3e

SHELL ["bash", "-euxo", "pipefail", "-c"]

ARG NODEMON_VERSION="2.0.15"
ARG YARN_VERSION="1.22.18"

RUN set -euxo pipefail >/dev/null \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get update -qq --yes \
&& apt-get install -qq --no-install-recommends --yes \
bash \
bash-completion \
build-essential \
ca-certificates \
curl \
git \
gnupg \
python3 \
python3-pip \
python3-setuptools \
python3-wheel \
sudo \
time \
>/dev/null \
&& apt-get clean autoclean >/dev/null \
&& apt-get autoremove --yes >/dev/null \
&& rm -rf /var/lib/apt/lists/*

ARG USER=user
ARG GROUP=user
ARG UID
ARG GID

ENV USER=$USER
ENV GROUP=$GROUP
ENV UID=$UID
ENV GID=$GID
ENV TERM="xterm-256color"
ENV HOME="/home/${USER}"
ENV NODE_DIR="/opt/node"
ENV PATH="${NODE_DIR}/bin:${HOME}/.local/bin:${PATH}"

# Install Node.js
COPY .nvmrc /
RUN set -eux >dev/null \
&& mkdir -p "${NODE_DIR}" \
&& cd "${NODE_DIR}" \
&& NODE_VERSION=$(cat /.nvmrc) \
&& curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" | tar -xJ --strip-components=1 \
&& npm install -g nodemon@${NODEMON_VERSION} yarn@${YARN_VERSION} >/dev/null

#&& npm config set scripts-prepend-node-path auto

# Calm down the (in)famous chatter from yarn
RUN set -euxo pipefail >/dev/null \
&& sed -i'' "s/this.reporter.warn(this.reporter.lang('incompatibleResolutionVersion', pattern, reqPattern));//g" "${NODE_DIR}/lib/node_modules/yarn/lib/cli.js" \
&& sed -i'' "s/_this2\.reporter.warn(_this2\.reporter.lang('ignoredScripts'));//g" "${NODE_DIR}/lib/node_modules/yarn/lib/cli.js" \
&& sed -i'' 's/_this3\.reporter\.warn(_this3\.reporter\.lang(peerError.*;//g' "/opt/node/lib/node_modules/yarn/lib/cli.js"


# Install Python dependencies
COPY requirements.txt /
RUN set -euxo pipefail >/dev/null \
&& pip3 install --user -r /requirements.txt


# Make a user and group
RUN set -euxo pipefail >/dev/null \
&& \
if [ -z "$(getent group ${GID})" ]; then \
addgroup --system --gid ${GID} ${GROUP}; \
else \
groupmod -n ${GROUP} $(getent group ${GID} | cut -d: -f1); \
fi \
&& \
if [ -z "$(getent passwd ${UID})" ]; then \
useradd \
--system \
--create-home --home-dir ${HOME} \
--shell /bin/bash \
--gid ${GROUP} \
--groups sudo \
--uid ${UID} \
${USER}; \
fi \
&& sed -i /etc/sudoers -re 's/^%sudo.*/%sudo ALL=(ALL:ALL) NOPASSWD: ALL/g' \
&& sed -i /etc/sudoers -re 's/^root.*/root ALL=(ALL:ALL) NOPASSWD: ALL/g' \
&& sed -i /etc/sudoers -re 's/^#includedir.*/## **Removed the include directive** ##"/g' \
&& echo "foo ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers \
&& touch ${HOME}/.hushlogin \
&& chown -R ${UID}:${GID} "${HOME}"

USER ${USER}
42 changes: 42 additions & 0 deletions infra/data/lambda-at-edge/OriginRequest.lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable prefer-destructuring,sonarjs/no-collapsible-if,unicorn/no-lonely-if */
// Implements rewrite of non-compressed to .gz URLs using AWS
// Lambda@Edge. This is useful if you have precompressed your files.
//
// Usage:
// Create an AWS Lambda function and attach it to "Origin Request" event of a
// Cloudfront distribution

const ARCHIVE_EXTS = ['.7z', '.br', '.bz2', '.gz', '.lzma', '.xz', '.zip', '.zst']

function getHeader(headers, headerName) {
const header = headers[headerName.toLowerCase()]
if (!header || !header[0] || !header[0].value) {
return undefined
}
return header[0].value
}

function acceptsEncoding(headers, encoding) {
const ae = getHeader(headers, 'Accept-Encoding')
if (!ae || typeof ae != 'string') {
return false
}
return ae.split(',').some((e) => e.trim().toLowerCase().startsWith(encoding.toLowerCase()))
}

function handler(event, context, callback) {
const request = event.Records[0].cf.request
const headers = request.headers

// If not an archive file (which are not precompressed), rewrite the URL to
// get the corresponding .gz file
if (ARCHIVE_EXTS.every((ext) => !request.uri.endsWith(ext))) {
if (acceptsEncoding(headers, 'gzip')) {
request.uri += '.gz'
}
}

callback(null, request)
}

exports.handler = handler
63 changes: 63 additions & 0 deletions infra/data/lambda-at-edge/ViewerResponse.lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Adds additional headers to the response, including security headers and CORS.
// Suited for serving data and APIs.
//
// See also:
// - https://securityheaders.com/
//
// Usage: Create an AWS Lambda@Edge function and attach it to "Viewer Response"
// event of a Cloudfront distribution

const NEW_HEADERS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Content-Security-Policy': `default-src 'none'; frame-ancestors 'none'`,
'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload',
'X-Content-Type-Options': 'nosniff',
'X-DNS-Prefetch-Control': 'off',
'X-Download-Options': 'noopen',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
}

function addHeaders(headersObject) {
return Object.fromEntries(
Object.entries(headersObject).map(([header, value]) => [header.toLowerCase(), [{ key: header, value }]]),
)
}

const HEADERS_TO_REMOVE = new Set(['server', 'via'])

function filterHeaders(headers) {
return Object.entries(headers).reduce((result, [key, value]) => {
if (HEADERS_TO_REMOVE.has(key.toLowerCase())) {
return result
}

if (key.toLowerCase().includes('powered-by')) {
return result
}

return { ...result, [key.toLowerCase()]: value }
}, {})
}

function modifyHeaders({ response }) {
let newHeaders = addHeaders(NEW_HEADERS)

newHeaders = {
...response.headers,
...newHeaders,
}

newHeaders = filterHeaders(newHeaders)

return newHeaders
}

exports.handler = (event, context, callback) => {
const { request, response } = event.Records[0].cf
response.headers = modifyHeaders({ request, response })
callback(null, response)
}

exports.modifyHeaders = modifyHeaders
12 changes: 12 additions & 0 deletions infra/find-lambda-at-edge-logs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail

# Inspired by https://stackoverflow.com/a/54096479/526860

FUNCTION_NAME=$1

for region in $(aws --output text ec2 describe-regions | cut -f 4); do
for loggroup in $(aws --output text logs describe-log-groups --log-group-name "/aws/lambda/us-east-1.$FUNCTION_NAME" --region $region --query 'logGroups[].logGroupName'); do
printf "$region\tconsole.aws.amazon.com/cloudwatch/home?region=$region#logsV2:log-groups/log-group/\$252Faws\$252Flambda\$252Fus-east-1.$FUNCTION_NAME\n"
done
done
Loading