Developer Blog

Tipps und Tricks für Entwickler und IT-Interessierte

Power Shell | Cookbook

Scripts

Parameter in a Powershell Script

param (
    [Parameter(Mandatory=$true)]  [string] $folder = "",
    [Parameter(Mandatory=$false)] [string] $type,
    [Parameter(Mandatory=$false)] [switch] $migrate,
    [Parameter(Mandatory=$false)] [switch] $help
)

Call the Script

.\script.ps1 foldername
.\script.ps1 foldername -type=folder

.\script.ps1 foldername -type=folder -migrate

Parameter Debug and Verbose

This parameter could not be defined in a script, because they are already present.

param (
    [Parameter(Mandatory=$false)] [switch] $debug
)
.\using_parameter.ps1 : Ein Parameter mit dem Namen "Debug" wurde mehrfach für den Befehl definiert.
In Zeile:1 Zeichen:1
+ .\using_parameter.ps1
+ ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], MetadataException
    + FullyQualifiedErrorId : ParameterNameAlreadyExistsForCommand

To access their values, use

param (
    [Parameter(Mandatory=$false)] [switch] $help
)

$debug   = $PSBoundParameters.ContainsKey('Debug')
$verbose = $PSBoundParameters.ContainsKey('Verbose')

Write-Host "help    = $help"
Write-Host "debug   = $debug"
Write-Host "verbose = $verbose"
> .\using_parameter.ps1  -debug -help
help    = True
debug   = True
verbose = False
> .\using_parameter.ps1  -debug 
help    = False
debug   = True
verbose = False
> .\using_parameter.ps1  -debug -verbose
help    = False
debug   = True
verbose = True

String

Converting String to TitleCase

function toTitleCase($string) {
    return $string.substring(0,1).toupper()+$string.substring(1).tolower()

}

Filesystem

List of Files in a Folder

CommandDescription
Get-ChildItemFolders and Files
Get-ChildItem -DirectoryOnly Folders
Get-ChildItem -FileOnly Files
Get-ChildItem -Recurse -DirectoryOnly Folders, Recursive
Get-ChildItem -Recurse -FileOnly Files, Recursive
(ls -r *.txt).fullname
Get-ChildItem -recurse -Filter .editorconfig -path . |



dir -Path . -Filter ProfileInformationController* -Recurse |



Counting Files

CommandDescription
(Get-ChildItem | Measure-Object).CountCount the files and folders
(Get-ChildItem -Directory | Measure-Object).CountOnly Folders
(Get-ChildItem -File | Measure-Object).CountOnly Files
(Get-ChildItem -Recurse -Directory | Measure-Object).CountOnly Folders, Recursive
(Get-ChildItem -Recurse -File | Measure-Object).CountOnly Files, Recursive

Using  Scripting.FileSystemObject

$objFSO = New-Object -com  Scripting.FileSystemObject
$objFSO.GetFolder($folder).Files.Count

Find a Folders within a Folder (not recursive)

(Get-ChildItem --Attributes Directory).Name

Run Command for each Folder

(Get-ChildItem  -Attributes Directory).Name |



With Enumerator

[IO.Directory]::EnumerateFiles($folder) | ForEach-Object {
    Write-Host $_
}

Find all folders with Pattern

Get-ChildItem -recurse -depth 2 -directory -path ..\packages_flutter | `
Where-Object { $_.FullName -match 'example' }                        | `



Delete files with pattern

Get-ChildItem *.code -recurse | foreach { Remove-Item -Path $_.FullName }
Get-ChildItem -Path C:Temp -Include *.* -File -Recurse | foreach { $_.Delete()}

Files

Filename and Extension of File with Split-Path

Filename

Split-Path -Path <PATH> -Leaf

Folder

Split-Path -Path <PATH>

Extension

Split-Path -Path <PATH> -Extension

Create File

New-Item new.txt -type file

Loop through entries of a textfile

Get-Content list-of-folders | Where-Object {$_ -NotMatch "#.*" } | ForEach-Object { 
    .\check_folder.ps1$_
    # Write-Host "Weiter -->" -NoNewLine
    # $key = $Host.UI.RawUI.ReadKey()
}

Loop through results from Select-String

Here is an example that greps for a string and uses the results in a loop to determine if some action should be taken

$pattern = "powershell"
$files = Select-String -Path "d:\script\*.txt" -Pattern $pattern
foreach ($file in $files) {
   $filename=$file.Filename
   $item = get-item $file.Path
    "File '{0}' matches the pattern '{1}'" -f $item.FullName, $pattern
    "It was last modified on {0}" -f $item.LastWriteTime
 
   $response = Read-Host -Prompt "Set the archive bit on this file?" 
   If ($response -eq 'Y') {
      $item.attributes="Archive"
   }
}

Check if a file exist

Test-Path $PROFILE

Searching in Files

Get-ChildItem -Recurse | Select-String "dummy" -List | Select Path
Get-ChildItem -Recurse *.sql | Select-String "create .*_tab_" | Select-Object -Unique Path
Select-String -path *.txt -pattern PowerShell

Select-String -path *.txt -pattern PowerShell -notmatch

Parsing Files

Get first line of output

$eventResult.Split([Environment]::NewLine) | Select -First 1

Web

Download Web-Page

Invoke-WebRequest -Uri <link>

List all Links

$response = Invoke-WebRequest -Uri <link>
$response.links | Select -Expand href

List all Links with filtering href by RegEx

$response.Links | Where-Object {$_.href -like "*videos*" } | ForEach-Object { $_.href }

List all Links with filtering class name by RegEx

$response.Links | Where-Object {$_.class -eq "page-numbers"} | Format-List innerText, href

Download and Install Visual Studio Code in portable Mode

$FOLDER=Get-Date -Format "yyyy-MM-dd-HH-mm"

Write-Host "Create link for folder $FOLDER"


# Download
# https://code.visualstudio.com/sha/download?build=stable&os=win32-x64-archive
# https://code.visualstudio.com/sha/download?build=insider&os=win32-x64-archive

$LINK="https://code.visualstudio.com/sha/download?build=insider&os=win32-x64-archive"
$FILE="vscode-insider.zip"

if (Test-Path "$FILE") {
	Remove-Item "$FILE"
}

Invoke-WebRequest "$LINK" -OutFile "$FILE"

Expand-Archive "$FILE" "$FOLDER"

if (Test-Path $FOLDER\data)
{
	Remove-Item $FOLDER\data
}


if (Test-Path code) { Remove-Item code }

# Using junction from SysInternalsSuite to create symlinks

junction code $FOLDER
junction code\data data

Environment

Show env variables

gci env:* | Sort-Object Name

Show env variables with name pattern

gci env: | Where name -like '*HOME

Processes

List Processes and Path

Get-Process | Select-Object Path

Show processes using a specific port

Get-Process -Id (Get-NetTCPConnection -LocalPort YourPortNumberHere).OwningProcess

Network

Pipeline

Parse out from command

$REPOSITORY=<URL of Repository>
git branch -r | ForEach-Object { Write-Output "git clone -b $_ $REPOSITORY $_" } | Out-File -FilePath .clone-all-branches

Permissions

Show current policy

Get-ExecutionPolicy

Allow custom scripts to execute

Set-ExecutionPolicy -Scope CurrentUser unrestricted

Security

Self-Sign a script

Step 1: Create your code signing certificate

New-SelfSignedCertificate -DnsName user@via-internet.de `
                          -CertStoreLocation Cert:\currentuser\my  `
                          -Subject "CN=Local Code Signing"  `
                          -KeyAlgorithm RSA  `
                          -KeyLength 2048  `
                          -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider"  `
                          -KeyExportPolicy Exportable  `
                          -KeyUsage DigitalSignature  `
                          -Type CodeSigningCert

Step 2: Open the Certificate Manager for Current User

certmgr /s my

Step 3: Copy the new certificate to the appropriate cert stores

From Personal folder into Trusted Root Certification Authorities and into Trusted Publishers stores.

German: Von Eigene Zertifikate nach Vertrauenswürdige Stammzertifizierungsstellen und Vertrauenswürdige Herausgeber

Step 4: Sign your PowerShell script with the new cert

$CERT=@(Get-ChildItem cert:\CurrentUser\My -CodeSigning)[1]
Set-AuthenticodeSignature .\HelloWorld.ps1 $CERT

Or

❯ Set-AuthenticodeSignature -FilePath .\HelloWorld.ps1 -Certificate (Get-ChildItem -Path Cert:CurrentUserMy -CodeSigningCert)

Final Check

❯ Get-AuthenticodeSignature .\HelloWorld.ps1

Github

Download Repositories

gh repo list <github username> --limit 1000 |



Profiles

Different PowerShell profiles

DescriptionPath
Current User, Current Host – console$Home[My ]DocumentsWindowsPowerShellProfile.ps1
Current User, All Hosts   $Home[My ]DocumentsProfile.ps1
All Users, Current Host – console   $PsHomeMicrosoft.PowerShell_profile.ps1
All Users, All Hosts      $PsHomeProfile.ps1
Current user, Current Host – ISE$Home[My ]DocumentsWindowsPowerShellMicrosoft.P owerShellISE_profile.ps1
 All users, Current Host – ISE  $PsHomeMicrosoft.PowerShellISE_profile.ps1

Show Path for all profiles

$PROFILE | Format-List * -Force

Create a new profile

New-Item $PROFILE.CurrentUserAllHosts -ItemType file -Force

Customizing

Theming

https://ohmyposh.dev/

Install Posh-Git and Oh-My-Posh.

Install-Module posh-git   -Scope CurrentUser
Install-Module oh-my-posh -Scope CurrentUser
Install-Module -Name PSReadLine -AllowPrerelease -Scope CurrentUser -Force -SkipPublisherCheck

Then run “notepad $PROFILE” and add these lines to the end:

Import-Module posh-git
Import-Module oh-my-posh

Set-Theme Paradox

Set a custom theme

Import-Module posh-git
Import-Module oh-my-posh
Set-Theme Paradox

Show current theme settings

$ThemeSettings
$ThemeSettings.CurrentThemeLocation

Customize Prompt

Show current Path

function prompt
{
    "PS " + $(get-location) + "> "
}

Randor Color

function prompt
{
    $random = new-object random
    $color=[System.ConsoleColor]$random.next(1,16)
    Write-Host ("PS " + $(get-location) +">") -nonewline -foregroundcolor $color
    return " "
}

Display current time at the end of prompt line (this will mess up you console buffer)

function prompt
{
    $oldposition = $host.ui.rawui.CursorPosition
    $Endline = $oldposition
    $Endline.X+=60
    $host.ui.rawui.CursorPosition = $Endline
    Write-Host $(get-date).Tostring("yyyy-MM-dd HH:mm:ss")
    $host.ui.rawui.CursorPosition = $oldposition
    Write-Host ("PS " + $(get-location) +">") -nonewline -foregroundcolor Magenta
    return " "
}

Show current user, host, current line number

$global:CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
function prompt
{
    $host.ui.rawui.WindowTitle = $CurrentUser.Name + " " + $Host.Name + " " + $Host.Version + " Line: " + $host.UI.RawUI.CursorPosition.Y
    Write-Host ("PS " + $(get-location) +">") -nonewline -foregroundcolor Magenta
    return " "
}

Weitere Anpassungsmöglichkeiten

https://www.norlunn.net/2019/10/07/powershell-customize-the-prompt/

Security

❯ New-SelfSignedCertificate -DnsName user@via-internet.de -CertStoreLocation Cert:CurrentUserMy -Type CodeSigning

   PSParentPath: Microsoft.PowerShell.SecurityCertificate::CurrentUserMy

Thumbprint                                Subject              EnhancedKeyUsageList
----------                                -------              --------------------
4AED871E6DB5FF3E85EB1625C5369DBDB3E120FD  CN=user@via-interne… Codesignatur
❯ Set-AuthenticodeSignature -FilePath demo.ps1 -Certificate (Get-ChildItem -Path Cert:CurrentUserMy -CodeSigningCert)

    Directory: D:TMP

SignerCertificate                         Status                               StatusMessage                     Path
-----------------                         ------                               -------------                     ----
4AED871E6DB5FF3E85EB1625C5369DBDB3E120FD  Valid                                Signature verified.               demo.ps1

Final Check

❯ Get-AuthenticodeSignature .demo.ps1

    Directory: D:CLOUDEnvironmentsKeycloakKeycloak12.0.1bin

SignerCertificate                         Status                                StatusMessage                    Path
-----------------                         ------                                -------------                    ---
4AED871E6DB5FF3E85EB1625C5369DBDB3E120FD  Valid                                 Signature verified.              demo.ps1

From Bash to Powershell

Alias for WHICH command

❯ (get-command FILE.EXE).Path
❯ Set-Alias where Get-Command
❯ where FILE.EXE

Snippets

Formatting

0..31 | ForEach-Object { 
    "{0,6}`t{1,6}`t{2,5}`t0x{0:X4}" -f $_,[Convert]::ToString($_,2), [Convert]::ToString($_,8) 
}

Select a Service By Name (Partial Match)

> Get-Service | Select-Object -ExpandProperty Name | Select-String -pattern 'ws'

CURL For Powershell

> (Invoke-WebRequest www.google.com).Content

List Environment Variables

> gci env: | sort name

Get PS Version

> $PSVersionTable

List Installed Programs

> Get-WmiObject -Class Win32_Product

Colored Header in Output

$prefix ="$prefix                   ".Substring(0,6)
$title  ="$title                    ".Substring(0,15)

$line = "$prefix ${title}: ${line}"
$fill = " " * ([Console]::WindowWidth - $line.length)
Write-Host "${line}${fill}"  -ForegroundColor White -BackgroundColor Blue -NoNewline

PySpark | Cookbook

Websites

  • The Blaze Ecosystem (Blaze)
  • Dask: Flexible library for parallel computing in Python.
  • DataShape: Data layout language for array programming. 
  • Odo: Shapeshifting for your data
    It efficiently migrates data from the source to the target through a network of conversions.

Reading Textfiles

from pyspark.sql import SparkSession

spark = SparkSession \
    .builder \
    .appName("Demo") \
    .config("spark.demo.config.option", "demo-value") \
    .getOrCreate()

df = spark.read.csv("input.csv",header=True,sep="|");
from pyspark import SparkContext
from pyspark.sql import SQLContext
import pandas as pd

sc = SparkContext('local','example') 
sql_sc = SQLContext(sc)

p_df= pd.read_csv('file.csv')  # assuming the file contains a header
# p_df = pd.read_csv('file.csv', names = ['column 1','column 2']) # if no header
s_df = sql_sc.createDataFrame(pandas_df)
sc.textFile("file.csv") \
    .map(lambda line: line.split(",")) \
    .filter(lambda line: len(line)>1) \
    .map(lambda line: (line[0],line[1])) \
    .collect()
sc.textFile("input.csv") \
    .map(lambda line: line.split(",")) \
    .filter(lambda line: len(line)<=1) \
    .collect()
spark.read.csv(
    "input.csv", header=True, mode="DROPMALFORMED", schema=schema
)
(spark.read
    .schema(schema)
    .option("header", "true")
    .option("mode", "DROPMALFORMED")
    .csv("input.csv"))

Read CSV file with known structure

from pyspark.sql.types import StructType, StructField
from pyspark.sql.types import DoubleType, IntegerType, StringType

schema = StructType([
    StructField("A", IntegerType()),
    StructField("B", DoubleType()),
    StructField("C", StringType())
])

(sqlContext
    .read
    .format("com.databricks.spark.csv")
    .schema(schema)
    .option("header", "true")
    .option("mode", "DROPMALFORMED")
    .load("input.csv"))

Jenkins | Build and Deploy a Groovy App

Introduction

Using Jenkins as an automation server for your development, you can automate such repeating tasks as testing and deploying your app.

Starting with a sample Groovy App (a simple calculator) with tests, you will learn how to integrate your app in Jenkins and build a pipeline, so that Jenkins runs the desired tasks every time, you change the code.

Prepare the sources

Clone the sample repository from Github.

You should clone the demo repository into you demo account, because you may change some file during this post., and you will not get write permissions for the demo repository.

Also, clone the repository to your local machine to see what our demo app looks like.

$ git clone https://github.com/jenkins-toolbox/SampleApp_GroovyCalculator
Cloning into 'SampleApp_GroovyCalculator'...
remote: Enumerating objects: 194, done.
remote: Counting objects: 100% (194/194), done.
remote: Compressing objects: 100% (138/138), done.
remote: Total 194 (delta 44), reused 137 (delta 23), pack-reused 0
Receiving objects: 100% (194/194), 93.40 KiB | 817.00 KiB/s, done.
Resolving deltas: 100% (44/44), done.

Go into the new create folder

$ cd SampleApp_GroovyCalculator/
$ ls
Jenkinsfile      README.md        bin              build.gradle     gradlew          src
Makefile         SampleCalculator build            gradle           settings.gradle

The first task, Jenkins will do in our pipeline: build your app

$ ./gradlew build

Because it’s the first time you start gradlew, the required software will be downloaded:

First: the current Gradle Version (Gradle is the Build Tool used by Groovy Projects)

Downloading https://services.gradle.org/distributions/gradle-6.2.1-bin.zip
………10%………20%………30%……….40%………50%………60%……….70%………80%………90%……….100%

Welcome to Gradle 6.2.1!

Here are the highlights of this release:
 - Dependency checksum and signature verification
 - Shareable read-only dependency cache
 - Documentation links in deprecation messages

For more details see https://docs.gradle.org/6.2.1/release-notes.html

Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details

After this, your app will be tested

> Task :test

Calculator02Spec > two plus two should equal four PASSED

Calculator01Spec > add: 2 + 3 PASSED

Calculator01Spec > subtract: 4 - 3 PASSED

Calculator01Spec > multiply: 2 * 3 PASSED

BUILD SUCCESSFUL in 34s
5 actionable tasks: 5 executed

Perform the build again

No download is required. The build is much quicker.

$ ./gradlew build

BUILD SUCCESSFUL in 1s
5 actionable tasks: 5 up-to-date

Now, test our app:

./gradlew clean test

> Task :test

Calculator02Spec > two plus two should equal four PASSED

Calculator01Spec > add: 2 + 3 PASSED

Calculator01Spec > subtract: 4 - 3 PASSED

Calculator01Spec > multiply: 2 * 3 PASSED

BUILD SUCCESSFUL in 4s
5 actionable tasks: 5 executed

Create a Jenkins Pipeline

Start by clicking on the BlueOcean menu item.

Hint: Blue Ocean is not installed with the default Jenkins installation.

You have to install the corresponding Plugins.

Select Manage JenkinsManage Plugins.

Then, select the tab Available and enter in the Filter box: Blue Ocean.

Install all plugins, that will be listed.

Next: Click on the New Pipeline to create your first Pipeline

Use the Item GitHub to specify, where our code is stored

Next, use your GitHub account.

Be sure, that you cloned the demo repository

Next, we select the demo repository SampleApp_GroovyCalculator

Click on Create Pipeline and after a few seconds, the pipeline is created.

Immediately after creating the pipeline, Jenkins is starting the pipeline and all steps included.

If everything went well, you see a positive status

Now, click on the pipeline (e.g. the text master or the status icon) and you will see the pipeline with all steps and their corresponding state.

If you, want to edit the pipeline, for example to add another step, like on the pencil in the header.

Click on Cancel to leave the Pipeline editor.

Hint: If you click on Save, all changes are pushed back to the repository and Jenkins starts the Pipeline again.

Run the Pipeline

If you want to run your pipeline, click on the rerun icon for your pipeline

Jenkins | Cookbook

Working with VS Code

Validate Jenkins File

Install VS Code Plugin Jenkins Pipeline Linter Connector

Add configuration in .vscode/settings.json

"jenkins.pipeline.linter.connector.crumbUrl": "<JENKINS_URL>/crumbIssuer/api/xml?xpath=concat(//crumbRequestField
"jenkins.pipeline.linter.connector.user": "<USERNAME>",
"jenkins.pipeline.linter.connector.pass": "<PASSWORD>",
"jenkins.pipeline.linter.connector.url": "<JENKINS_URL>/pipeline-model-converter/validate",

Replace <USERNAME>, <PASSWORD> and <JENKINS_URL> with your values, for example

"jenkins.pipeline.linter.connector.crumbUrl": "http://localhost:8080/crumbIssuer/api/xml?xpath=concat(//crumbRequestField
"jenkins.pipeline.linter.connector.user": "admin",
"jenkins.pipeline.linter.connector.pass": "secret",
"jenkins.pipeline.linter.connector.url": "http://localhost:8080/pipeline-model-converter/validate",

Working with Jenkins Client (CLI)

Download Client

wget localhost:8080/jnlpJars/jenkins-cli.jar

Working with Plugins

Create aPlugin

mkdir SamplePlugin
cd SamplePlugin
mvn -U archetype:generate -Dfilter="io.jenkins.archetypes:"
mvn -U archetype:generate -Dfilter="io.jenkins.archetypes:global-configuration-plugin"
[INFO] Scanning for projects...
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml
Downloading from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/maven-metadata.xml
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml (14 kB at 32 kB/s)
Downloaded from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/maven-metadata.xml (20 kB at 44 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-archetype-plugin/maven-metadata.xml
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-archetype-plugin/maven-metadata.xml (918 B at 18 kB/s)
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.1.2:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.1.2:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.1.2:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> io.jenkins.archetypes:global-configuration-plugin (Skeleton of a Jenkins plugin with a POM and an example piece of global configuration.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1
Choose io.jenkins.archetypes:global-configuration-plugin version:
1: 1.2
2: 1.3
3: 1.4
4: 1.5
5: 1.6
Choose a number: 5:
[INFO] Using property: groupId = unused
Define value for property 'artifactId': com.examples.jenkins.plugins
Define value for property 'version' 1.0-SNAPSHOT: :
[INFO] Using property: package = io.jenkins.plugins.sample
Confirm properties configuration:
groupId: unused
artifactId: com.examples.jenkins.plugins
version: 1.0-SNAPSHOT
package: io.jenkins.plugins.sample
 Y: : y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: global-configuration-plugin:1.6
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: unused
[INFO] Parameter: artifactId, Value: com.examples.jenkins.plugins
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: io.jenkins.plugins.sample
[INFO] Parameter: packageInPathFormat, Value: io/jenkins/plugins/sample
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: io.jenkins.plugins.sample
[INFO] Parameter: groupId, Value: unused
[INFO] Parameter: artifactId, Value: com.examples.jenkins.plugins
[INFO] Project created from Archetype in dir: /Users/Shared/CLOUD/Kunde.BSH/workspace/SamplePlugin_Config/com.examples.jenkins.plugins
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  45.525 s
[INFO] Finished at: 2020-03-01T17:28:27+01:00
[INFO] ------------------------------------------------------------------------

Verify Plugin

 cd com.examples.jenkins.plugins
mvn verify

Run Plugin

mvn hpi:run

Working with Groovy Scripts

Include a common groovy script in Jenkins file

1: Create a common.groovy file with function as needed

def mycommoncode() {
}

2: In the main Jenkinfile load the file and use the function as shown below

node{ 
   def common = load “common.groovy”
   common.mycommoncode()
}

Basic example of Loading Groovy scripts

File example.groovy

def example1() {
 println 'Hello from example1' 
}
def example2() {
 println 'Hello from example2'
}

The example.groovy script defines example1 and example2 functions before ending with return this. Note that return this is definitely required and one common mistake is to forget ending the Groovy script with it.Jenkinsfile

def code node('java-agent') { 
    stage('Checkout') { checkout scm } 
    stage('Load') { code = load 'example.groovy' } 
    stage('Execute') { code.example1() }
} 

code.example2()

Processing Github JSON from Groovy

In this demo, we first show how to process JSON response from Github API in Groovy.Processing JSON from Github

String username = System.getenv('GITHUB_USERNAME') 
String password = System.getenv('GITHUB_PASSWORD') 
String GITHUB_API = 'https://api.github.com/repos' String repo = 'groovy' 
String PR_ID = '2' // Pull request ID 
String url = "${GITHUB_API}/${username}/${repo}/pulls/${PR_ID}" 

println "Querying ${url}" 

def text = url.toURL().getText(requestProperties: ['Authorization': "token ${password}"]) 
def json = new JsonSlurper().parseText(text) 

def bodyText = json.body // Check if Pull Request body has certain text if ( bodyText.find('Safari') ) {
     println 'Found Safari user' }

The equivalent bash command for retrieving JSON response from Github API is as follows:Equivalent bash command

// Groovy formatted string 
String cmd = "curl -s -H \"Authorization: token ${password}\" ${url}" 
// Example String 
example = 'curl -s -H "Authorization: token XXX" https://api.github.com/repos/tdongsi/groovy/pulls/2'

Processing Github JSON from Jenkinsfile

Continuing the demo from the last section, we now put the Groovy code into a callable function in a script called “github.groovy”. Then, in our Jenkinsfile, we proceed to load the script and use the function to process JSON response from Github API.github.groovy

import groovy.json.JsonSlurper
def getPrBody(String githubUsername, String githubToken, String repo, String id) {
   String GITHUB_API = 'https://api.github.com/repos' 
   String url = "${GITHUB_API}/${githubUsername}/${repo}/pulls/${id}" 

   println "Querying ${url}"

def text = url.toURL().getText(requestProperties: ['Authorization': "token ${githubToken}"])
def json = new JsonSlurper().parseText(text) 
def bodyText = json.body return bodyText } 
return this

Jenkinsfile

def code node('java-agent') { 
    stage('Checkout') { checkout scm }
    stage('Load') { code = load 'github.groovy' } 
    stage('Execute') {

Best Practice

Django | Build a Dashboard with Django and Bootstrap: Part 3

This is Part 3 of Building a Dashboard with Django and Bootstrap:

Introduction

If you follow the first part of this blog topic, you have a running Django dashboard.

But, the content ist still static. Lets review the current state:

Prepare our Django project

Right now, the whole content of our Django project is provided by the dashboard template

dashboard/template/site/base.html

Looking at our web site, you will see the different side menu items. So, intentionally, our web site should display different pages. And each page should provide the dynamic content.

The final goal of this part is to change our web app, so that each side item navigates us to a different page. For this, we have to take care about two things:

  • Navigation: how to we get to another page in our app
  • Project Structure: where to place the required components for each page

Basics of Navigation

Navigation usually is the process of getting from one page to another by clicking on a link.

So, we need to things:

  • the source page, containing the link
  • the destination page
  • the link, pointing to the destination page

Let’s take a look into the site template with the side menu:

The corresponding code in the side template is

<div id="collapseTwo" class="collapse" aria-labelledby="headingTwo">
	<div class="bg-white py-2 collapse-inner rounded">
		<h6 class="collapse-header">Custom Components:</h6>
		<a class="collapse-item" href="buttons.html">Buttons</a>
		<a class="collapse-item" href="cards.html">Cards</a>
	</div>
</div>

This is the result:

Linking to a html page is not possible, because Django does not work with html pages. Navigation in Django works with urls (in urls.py) and views in (views.py).

We must replace the html link tag (<a href="buttons.html">) with an Django-conform code. Read here for more details and the basics.

The idea behind the navigation is

Define the needed links/buttons
Give each link a name“buttons”
Define, which view to call for this linkcomponents/buttons/views.py
Tell Django, how to insert this link in a html page<a href="

With this in mind, we change our site template for the side navigation (e. g. for the components menu):

But, if you save the template and try to view the web page, you will see this error:

We missed to tell Django, what to do when the corresponding link for this name is requested. We have to tell Django to use the view defined in buttons/views.py to generate the resulting view/page.

So, change the global url mapping file dashboard/urls.py

import dashboard.apps.components.buttons.views as ButtonsViews
import dashboard.apps.components.cards.views as CardsViews

urlpatterns = [
    path('', include('dashboard.apps.urls')),

    path('buttons', 	ButtonsViews.IndexView.as_view(),   name='buttons'),
    path('cards',       CardsViews.IndexView.as_view(),     name='cards'),

    path('admin/', admin.site.urls),
]

dashboard/apps/components/buttons/views.py

from django.shortcuts import render
from django.views import generic

class IndexView(generic.TemplateView):
    template_name = 'buttons/base.html'

dashboard/apps/components/cards/views.py

from django.shortcuts import render
from django.views import generic

class IndexView(generic.TemplateView):
    template_name = 'cards/base.html'

dashboard/apps/components/cards/templates/cards/base.html




<h1 style="text-align: center">CARDS</h1>




dashboard/apps/components/cards/templates/buttons/base.html




<h1 style="text-align: center">BUTTONS</h1>




Save everything and view at the resulting page

What happens, from showing the page, clicking on the link until you see the final result:

  • Django create the side menu item Cards
  • with the corresponding link localhost:9000/cards
  • which will be the destination page, if you click on the link
  • and finally, Django creates the desired page (through view.py)

Namespaces and structure

Now, think about the names, e. g. buttons and cards in our Components.

Your project is getting bigger and you plan to add an additional page info with general information for both Components and Utilities menu.

So, you will have to additional files, for example

  • dashboard/apps/components/info/views.py
  • dashboard/apps/utilities/info/views.py

The corresponding url mapping (urls.py) could look like this:

import dashboard.apps.components.info.views as ComponentInfoViews
import dashboard.apps.utilities.info.views as UtilitiesInfoViews

urlpatterns = [
    path('', include('dashboard.apps.urls')),

    path('info', ComponentInfoViews.IndexView.as_view(), name='info'),
    path('info', UtilitiesInfoViews.IndexView.as_view(), name='info'),

Two pages with the same name (info) in different views.py!

Of course, we could choose different names and link (component_info, utilities_info), but this not a good programming style.

We will choose a more modern way of programming

  • Spread the responsibility over separate, independent modules
  • Name these modules with different names

What does this mean? We will have

  • a separate module frontend, only responsible for the start page (frontend)
  • a separate module components, responsible for all components
  • a separate module utilities, responsible for all utilities
  • a separate module pages, responsible for all pages

Resulting folder structure and file content

File dashbard/urls.py

urlpatterns = [
    path('', include('dashboard.apps.urls')),
    path('admin/',	admin.site.urls),
]

File dashbard/apps/urls.py

from django.urls import path
from django.urls.conf import include

from dashboard.apps.frontend.views import IndexView

app_name = 'app'

urlpatterns = [
    path('', IndexView.as_view(), name='index'),
    path('pages/', include('dashboard.apps.pages.urls')),
    path('components/', include('dashboard.apps.components.urls')),
    path('utilities/', include('dashboard.apps.utilities.urls')),
]

File dashbard/apps/components/urls.py

from django.urls import path

import dashboard.apps.components.buttons.views as ButtonsViews
import dashboard.apps.components.cards.views as CardsViews

app_name = 'components'

urlpatterns = [
    path('', ButtonsViews.IndexView.as_view(), name='index'),
    path('buttons/', ButtonsViews.IndexView.as_view(), name='buttons'),
    path('cards/', CardsViews.IndexView.as_view(), name='cards'),
]

File dashbard/apps/utilities/urls.py

from django.urls import path

import dashboard.apps.utilities.colors.views as ColorsViews
import dashboard.apps.utilities.borders.views as BordersViews
import dashboard.apps.utilities.animations.views as AnimationsViews
import dashboard.apps.utilities.others.views as OthersViews

app_name = 'utilities'

urlpatterns = [
    path('', BordersViews.IndexView.as_view(), name='index'),
    path('animations/', AnimationsViews.IndexView.as_view(), name='animations'),
    path('borders/', BordersViews.IndexView.as_view(), name='borders'),
    path('colors/', ColorsViews.IndexView.as_view(), name='colors'),
    path('others/', OthersViews.IndexView.as_view(), name='others'),
]

File dashbard/apps/pages/urls.py

from django.urls import path

import dashboard.apps.pages.blank.views as BlankViews
import dashboard.apps.pages.login.views as LoginViews
import dashboard.apps.pages.pagenotfound.views as PageNotFoundViews
import dashboard.apps.pages.password.views as PasswordViews
import dashboard.apps.pages.register.views as RegisterViews
import dashboard.apps.pages.charts.views as ChartsViews
import dashboard.apps.pages.tables.views as TablesViews

app_name = 'pages'

urlpatterns = [
    path('', ChartsViews.IndexView.as_view(), name='index'),
    path('blank', BlankViews.IndexView.as_view(), name='blank'),
    path('charts', ChartsViews.IndexView.as_view(), name='charts'),
    path('login', LoginViews.IndexView.as_view(), name='login'),
    path('pagenotfound', PageNotFoundViews.IndexView.as_view(), name='pagenotfound'),
    path('password', PasswordViews.IndexView.as_view(), name='password'),
    path('register', RegisterViews.IndexView.as_view(), name='register'),
    path('tables', TablesViews.IndexView.as_view(), name='tables'),
]

Let’s finally check the namespace structure:

$ find . -name urls.py
./dashboard/urls.py
./dashboard/apps/utilities/urls.py
./dashboard/apps/components/urls.py
./dashboard/apps/urls.py
./dashboard/apps/pages/urls.py

We create three levels for our namespaces:

Djane URL FileNamespace
./dashboard/urls.py
./dashboard/apps/urls.pyapp
./dashboard/apps/utilities/urls.py
./dashboard/apps/components/urls.py
./dashboard/apps/pages/urls.py
app:utilities
app:components
app:pages

These namespaces must be used in the template files, for example:

<a href="
<a href="



<a class="collapse-item" href="
<a class="collapse-item" href="
<a class="collapse-item" href="
<a class="collapse-item" href="



Install the Django Extensions for additional commands:

pip install django-extensions

Add Django Extensions to the INSTALLED_APPS

INSTALLED_APPS = [
    ...

    'django_extensions'
]

Show URLs and Namespaces (only for out apps, admin urls are removed)

python3 manage.py show_urls

Preparing required components and pages

In summary, these are the steps to create the desired folder structure:

mkdir -p dashboard/apps/components/buttons/templates/buttons
mkdir -p dashboard/apps/components/cards/templates/cards
mkdir -p dashboard/apps/pages/blank/templates/blank
mkdir -p dashboard/apps/pages/charts/templates/charts
mkdir -p dashboard/apps/pages/login/templates/login
mkdir -p dashboard/apps/pages/pagenotfound/templates/pagenotfound
mkdir -p dashboard/apps/pages/password/templates/password
mkdir -p dashboard/apps/pages/register/templates/register
mkdir -p dashboard/apps/pages/tables/templates/tables
mkdir -p dashboard/apps/utilities/animations/templates/animations
mkdir -p dashboard/apps/utilities/borders/templates/borders
mkdir -p dashboard/apps/utilities/colors/templates/colors
mkdir -p dashboard/apps/utilities/others/templates/others
python3 manage.py startapp buttons dashboard/apps/components/buttons
python3 manage.py startapp cards dashboard/apps/components/cards
python3 manage.py startapp blank dashboard/apps/pages/blank
python3 manage.py startapp charts dashboard/apps/pages/charts
python3 manage.py startapp login dashboard/apps/pages/login
python3 manage.py startapp pagenotfound dashboard/apps/pages/pagenotfound
python3 manage.py startapp password dashboard/apps/pages/password
python3 manage.py startapp register dashboard/apps/pages/register
python3 manage.py startapp tables dashboard/apps/pages/tables
python3 manage.py startapp animations dashboard/apps/utilities/animations
python3 manage.py startapp borders dashboard/apps/utilities/borders
python3 manage.py startapp colors dashboard/apps/utilities/colors
python3 manage.py startapp others dashboard/apps/utilities/others
echo "






cp base.html dashboard/apps/components/buttons/templates/buttons
cp base.html dashboard/apps/components/cards/templates/cards
cp base.html dashboard/apps/pages/blank/templates/blank
cp base.html dashboard/apps/pages/charts/templates/charts
cp base.html dashboard/apps/pages/login/templates/login
cp base.html dashboard/apps/pages/pagenotfound/templates/pagenotfound
cp base.html dashboard/apps/pages/password/templates/password
cp base.html dashboard/apps/pages/register/templates/register
cp base.html dashboard/apps/pages/tables/templates/tables
cp base.html dashboard/apps/utilities/animations/templates/animations
cp base.html dashboard/apps/utilities/borders/templates/borders
cp base.html dashboard/apps/utilities/colors/templates/colors
cp base.html dashboard/apps/utilities/others/templates/others
rm base.html

Each of the folders has the same structure, for example the buttons component. We will delete some unnecessary files

Replacing Projects with dynamic data

Replacing the static parts with dynamic content could be achieved by the following approach:

  • Identify the dynamic parts
  • Move these parts from the site template into the view template base.html of the component
  • Modify frontend view.py to generate dynamic content from data

The steps are the same for all components (all items of the side menu).

Find the

Identify dynamic parts in template

Create templates for side menu pages

For every side menu item, their is a corresponding html file in the install folder of the sb-admin-2 template:

Remember the environment variable we create in part 1 for the start of our project

DASHBOARD_ROOT=$(pwd)
cd $DASHBOARD_ROOT
find dashboard/apps install/sb-admin-2 -name *.html

Each template file base.html has a corresponding html file unter sb-admin-2. Look at the following table to find the mapping:

apps/components/buttons/templates/buttons/base.htmlsb-admin-2/buttons.html
apps/components/cards/templates/cards/base.htmlsb-admin-2/cards.html
apps/pages/blank/templates/blank/base.htmlsb-admin-2/blank.html
apps/pages/charts/templates/charts/base.htmlsb-admin-2/charts.html
apps/pages/login/templates/login/base.htmlsb-admin-2/login.html
apps/pages/pagenotfound/templates/pagenotfound/base.htmlsb-admin-2/404.html
apps/pages/password/templates/password/base.htmlsb-admin-2/forgot-password.html
apps/pages/register/templates/register/base.htmlsb-admin-2/register.html
apps/pages/register/templates/tables/base.htmlsb-admin-2/tables.html
apps/utilities/animations/templates/animations/base.htmlsb-admin-2/utilities-animation.html
apps/utilities/borders/templates/borders/base.htmlsb-admin-2/utilities-border.html
apps/utilities/colors/templates/colors/base.htmlsb-admin-2/utilities-color.html
apps/utilities/others/templates/others/base.htmlsb-admin-2/utilities-html

Each template base.html should have the following content:







And each corresponding view.py file should have the following content, only the template_name should be different (the name of the template base.html file)

from django.views import generic

class IndexView(generic.TemplateView):
    template_name = 'buttons/base.html'

So, for each template file, we have to

  • locate the corresponding html file from the install folder (see table above)
  • copy the content between these tags to the template file:
        <!-- Begin Page Content -->
        <div class="container-fluid">
        ....
        </div>
        <!-- /.container-fluid -->

Django | Build a Dashboard with Django and Bootstrap: Part 2

This is Part 2 of Building a Dashboard with Django and Bootstrap:

Introduction

If you follow the first part of this blog topic, you have a running Django dashboard.

But, unfortunately, the content is still static. Let’s review the current state:

Perfect. We are done with the basic setup.

Still, some work to do, because our dashboard is only a static dashboard. All content is programmed in the dashboard template file dashboard/templates/site/sb-admin-2/base.html

For example, look at the cards with the earnings at the top:

To achieve a more dynamic content, we need to move the desired parts of the dashboard from the template file to the frontend view file.

We will do this by following these steps:

  • Identify the dynamic parts
  • Move these parts from the template into for frontend view template index.html
  • Modify frontend view.py to generate dynamic content from data

Identify dynamic parts

How to find the parts, which are dynamic.

One way is to ask:

  • Which parts should be on every page (unchanged) and
  • What should change on every page

You mostly get the same answers by the question:

  • What are the main components of a web page (including navigation and content)

For answer the first question, take a look at the current page and “name” the areas:

So, these “names” are also the answer for the 3. Question:

  • sidemenu
  • top bar
  • content

And maybe you find additional “names”

  • header
  • footer
  • top menu
  • left and right sidebar

Find identified parts in template

Next step is, to find the identified parts in our dashboard template

dashboard/templates/site/sb-admin-2/base.html

This is an easy step, because the developer of the SB Admin 2 template documented their template well:

Looking into the code of the template, you will find comment lines describing the content:

  • <!-- Sidebar -->
  • <!-- Topbar -->
  • <!-- Top Search -->
  • <!-- Top Navbar -->
  • <!-- Begin Page Content-->

So, it is obvious what do to next:

  • get the dynamic part (lines under)<!-- Begin Page Content-->
    the green box in the following image
  • move it to the frontend template
  • place some information in the dashboard template, that the real content should be displayed here
    the blue curly braces in the following image

This is the way, the template system of django works:

Let’s explain this with a simple example: the page title

We declare a title (which should be considered as the default title). And in the frontend page, we declare the title for this page (the frontend page).

To achieve this, we have to tell our template system the following:

Now, we do the same with the content:

Looking at our resulting page, nothing changes. This is the desired result, but how could we be sure, that we really change the structure?

Well, let’s make a test and try to include a different content in the dashboard template.

Change the lines, where we include the content into this:

MISSING CONTENT




Did you notice the other name of the content: content_missing?

Change the template, save the file and have a look at the result:

Change content back, so your template is working again:

MISSING CONTENT




The final step in Part 3 will be replacing all static content of the dashboard with dynamic content.

Django | Build a Dashboard with Django and Bootstrap: Part 1

This is Part 1 of Building a Dashboard with Django and Bootstrap:

Introduction

Building a complete web app isn’t always an easy task. Designing and Implementing on both sides (backend and front-end) requires mostly a lot of knowledge. So, why don’t using tools or framework, which helps and makes our life easier. Django is one of these frameworks:

Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development

So, let’s get started.

Create project

For subsequent steps, we will remember our starting directory

❯ DASHBOARD_ROOT=$(pwd)
❯ echo $DASHBOARD_ROOT
... here you will see your current folder...

First step is to create our main Django project

❯ django-admin startproject dashboard
❯ mv dashboard project
❯ cd project
❯ python manage.py migrate
❯ python manage.py runserver 8080
...

Starting development server at http://127.0.0.1:8080/
Quit the server with CTRL-BREAK.

View current project in browser

Create first apps

❯ mkdir -p apps/core
❯ python manage.py  startapp Core apps/core

❯ mkdir -p apps/frontend
❯ python manage.py startapp Frontend apps/frontend

The folder structure should look like this:

Add new apps to project

Modify name in apps/core/apps.py

class CoreConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'apps.core'

Modify name in apps/frontend/apps.py

class FrontendConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'apps.frontend'

Modify dashboard/settings.py

INSTALLED_APPS = [
   ...

    'apps.core',
    'apps.frontend',
]

Modify dashboard/urls.py

from django.contrib import admin
from django.urls import path

import apps.frontend.views as views

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('admin/', admin.site.urls),
]

Create view

Modify view in apps/frontend/views.py

from django.shortcuts import render
from django.views import generic


class IndexView(generic.TemplateView):
    """
    IndexView:
    """
    module = 'indexView'
    template_name = 'frontend/index.html'

Create template for frontend View

Create template file apps/frontend/templates/frontend/index.html

<h1>
Frontend: Let's get started
</h1>

Add template folder to configuration

In dashboard/settings.py, add template folder to TEMPLATES

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
]

View current project in browser

Current folder Structure

So far, we have the following folder structure

Prepare for administrate your project

Create admin user

❯ python manage.py createsuperuser
Username (leave blank to use 'user'): admin
Email address: admin@localhost
Password: 
Password (again): 
Superuser created successfully.

Add Bootstrap support

Download requires files for Bootstrap, jQuery and PopperJS.

Or download this prepared archiv and unzip the files unter dashboard.

Run the scripts in $DASHBOARD_ROOT

Hint: You need to install PowerShell before running the scripts.

> cd $DASHBOARD_ROOT
> ./download_bootstrap.ps1
> ./download_jquery.ps1
> ./download_popperjs.ps1

download_bootstrap.ps1

#!/usr/bin/env pwsh

$ROOT = Split-Path -Parent $PSSCRIPTROOT

$global:ProgressPreference  = 'SilentlyContinue'  
$response = Invoke-WebRequest https://getbootstrap.com/ 
$downloadlink = $response.links | Where-Object { $_.href -like "*download/" } | foreach-object { $_.href } | select-object -first 1 
$downloadpage = Invoke-WebRequest https://getbootstrap.com$downloadlink

$dist_download_url = $downloadpage.links | where-object { $_.href -like "*-dist.zip" } | foreach-object { $_.href }
$dist_version      = $dist_download_url.split("/")[-2].replace("v","")
$dist_zip          = "$ROOT\${dist_version}.zip"

Write-Host "Download $dist_zip from $dist_download_url"
Invoke-WebRequest $dist_download_url -O $dist_zip

Write-Host "Unpack to assets\vendor\bootstrap\${dist_version}"

$fldr_bootstrap = "project\dashboard\static\assets\vendor\bootstrap"

if (Test-Path -Path $fldr_bootstrap) {
    Remove-Item -recurse -force           $fldr_bootstrap
}

New-Item -type directory                  $fldr_bootstrap > $null 
Expand-Archive $dist_zip -destinationpath $fldr_bootstrap

Move-Item $fldr_bootstrap\bootstrap* $fldr_bootstrap\${dist_version}

$global:ProgressPreference  = 'Continue'

download_jquery.ps1

#!/usr/bin/env pwsh

$ROOT = Split-Path -Parent $PSSCRIPTROOT

$version = "3.7.0"

$url_base = "https://code.jquery.com"

$destination = "project\dashboard\static\assets\vendor\jquery\$version\js"

Write-Host "Prepare  destination $destination"
if (Test-Path -Path $destination) {
    Remove-Item -recurse -force           $destination > $null
}

New-Item -type directory                  $destination > $null 

Invoke-WebRequest $url_base/jquery-${version}.js      -O $destination/jquery-${version}.js
Invoke-WebRequest $url_base/jquery-${version}.min.map -O $destination/jquery-${version}.min.map

download_popperjs.ps1

#!/usr/bin/env pwsh

$ROOT = Split-Path -Parent $PSSCRIPTROOT

$version = "2.11.8"

$url_base = "https://cdnjs.cloudflare.com/ajax/libs/popper.js/${version}/umd/"

$destination = "project\dashboard\static\assets\vendor\popperjs\$version\js"

Write-Host "Prepare  destination $destination"
if (Test-Path -Path $destination) {
    Remove-Item -recurse -force           $destination > $null
}

New-Item -type directory                  $destination > $null 

Invoke-WebRequest $url_base/popper.js -O  $destination/popper.js

Finally, the folder structure should look like this:

Create site templates in dashboard/templates/site

Add templates path to settings

File dashboard/settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / '/dashboard/templates',
        ],
        ...

Add static path to settings

File dashboard/settings.py

Add as first line

import os

Then add / replace at STATIC_URL=...

STATIC_URL = 'static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'dashboard/static')
]

Modify frontend view template

File dashboard/apps/frontend/templates/index.html






<main>
	<div class="p-5 mb-4 bg-body-tertiary rounded-3">
		<div class="container-fluid py-5">
			<h1 class="display-5 fw-bold">Custom jumbotron</h1>
			<p class="col-md-8 fs-4">Using a series of utilities, you can create this jumbotron, just like the one in
				previous versions of Bootstrap. Check out the examples below for how you can remix and restyle it to
				your liking.</p>
			<button class="btn btn-primary btn-lg" type="button">Example button</button>
		</div>
	</div>
	</div>
</main>




File dashboard/apps/frontend/templates/site/base.html

<!DOCTYPE html>
<html>

<head>
    <title>
    <link rel="stylesheet" href="
</head>

<body>

    <nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">Navigation</a>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarCollapse">
            <ul class="navbar-nav me-auto mb-2 mb-md-0">
              <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Home</a></li>
              <li class="nav-item"><a class="nav-link" href="polls">Polls</a>
              </li>
            </ul>
          </div>
        </div>
      </nav>

    <div class="container">
        
        
    </div>

    <script src="
</body>

</html>

View current status of project

Final Result

The final result could be found here.

Add dashboard from an existing template

For a first start, we will use this sb-admin-2 dashboard template from here:

Download required files

Bootstrap templates consist of at least 3 different types of files

  • main index.html page, responsible for collection all elements and set up your page
  • CSS files defining the style of your page
  • JavaScript files, containing needed frameworks and code

So, first start by downloading the sample template from here. Be sure, you start in our project root folder:

❯ cd $DASHBOARD_ROOT
❯ git clone https://github.com/BlackrockDigital/startbootstrap-sb-admin-2 install/sb-admin-2

Next, find out, what we need for our template in addition to the file index.html

❯ cd install/sb-admin-2
❯ grep -E "<(link|script)" index.html | grep -E "(href|src)="
  <link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
  <link href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">
  <link href="css/sb-admin-2.min.css" rel="stylesheet">
  <script src="vendor/jquery/jquery.min.js"></script>
  <script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
  <script src="vendor/jquery-easing/jquery.easing.min.js"></script>
  <script src="js/sb-admin-2.min.js"></script>
  <script src="vendor/chart.js/Chart.min.js"></script>
  <script src="js/demo/chart-area-demo.js"></script>
  <script src="js/demo/chart-pie-demo.js"></script>

That’s a lot of additional files.

To keep the file structure consistent, these files should be stored under the dashboard/static folder (same as the bootstrap files before).

To achieve this, there are two possibilities:

  • Create desired folder and copy each of the source files to the destination folder
  • Copy the complete static folder from the Github Repository for this post.

We use the second option to keep the focus on creating our dashboard.

So, first, clone the repository:

cd $DASHBOARD_ROOT/install
https://github.com/r14r/Django_Dashboard-with-Django-and-Bootstrap

Then, copy the requred files

cd $DASHBOARD_ROOT

cp -R install/Django_Dashboard-with-Django-and-Bootstrap/project/dashboard/static project/dashboard
cp -R install/Django_Dashboard-with-Django-and-Bootstrap/project/dashboard/templates dashboard

Having everything needed for the dashboard template, the next step is to modify the front-end template

File dashboard/apps/frontend/templates/frontend/index.html








View current project in browser

Perfect. We are done with the basic setup.

Still some work to do, because our dashboard is only a static dashboard. All content is programmed in the dashboard template file dashboard/templates/site/sb-admin-2/base.html

For example, look at the cards with the earnings at the top:

To achieve a more dynamic content, we need to move the desired parts of the dashboard from the template file to the front-end view file.

This will be described in the next step: Part 2: Prepare for dynamic content

Angular | Working with State Management

Introduction

One of the most challenging things in software development is state management.

Motivation

What is a state (or application state)? Theoretically, it is the entire memory of the application, but, typically, it is the data received via API calls, user inputs, presentation UI State, app preferences, etc.

It is the data that can differentiate two instances of the same application.

One example of application state would be a list of customers or products maintained in an application.

Problem (we’re trying to solve)

Think of an application using a list of data (products, customers, …). This list is the state that we are trying to manage.

Some API calls and user inputs could change the state ( i.e. the list ) by adding or removing items/entries. The state change should be reflected in the UI and other dependent components.

We could solve this with a global variable to hold the list and then add/remove customers from/to it and then write the code to update the UI and dependencies. But, there are many pitfalls in that design which are not the focus of this article.

Solutions

Currently there are several state management libraries for Angular apps: NGRX, NGXS or Akita.

RxJS – Reactive Extensions Library for JavaScript

RxJS is a library for reactive programming using Observables, to make it easier to compose asynchronous or callback-based code. This project is a rewrite of Reactive-Extensions/RxJS with better performance, better modularity, better debuggable call stacks, while staying mostly backwards compatible, with some breaking changes that reduce the API surface

Vorschau(öffnet in neuem Tab)

@ngrx/store

Store is RxJS powered state management for Angular applications, inspired by Redux. Store is a controlled state container designed to help write performant, consistent applications on top of Angular.

Key concepts

  • Actions describe unique events that are dispatched from components and services.
  • State changes are handled by pure functions called reducers that take the current state and the latest action to compute a new state.
  • Selectors are pure functions used to select, derive and compose pieces of state.
  • State is accessed with the Store, an observable of state and an observer of actions
NgRx State Management Lifecycle Diagram

Akita

Akita is a state management pattern, built on top of RxJS, which takes the idea of multiple data stores from Flux and the immutable updates from Redux, along with the concept of streaming data, to create the Observable Data Stores model.

Akita encourages simplicity. It saves you the hassle of creating boilerplate code and offers powerful tools with a moderate learning curve, suitable for both experienced and inexperienced developers alike.

Akita is based on object-oriented design principles instead of functional programming, so developers with OOP experience should feel right at home. Its opinionated structure provides your team with a fixed pattern that cannot be deviated from.

NGXS – State management pattern + library for Angular

NGXS is a state management pattern + library for Angular. It acts as a single source of truth for your application’s state, providing simple rules for predictable state mutations.

NGXS is modeled after the CQRS pattern popularly implemented in libraries like Redux and NgRx but reduces boilerplate by using modern TypeScript features such as classes and decorators.

Introduction (taken from here)

What is state?

State is basically everything that will define the UI that our user will be using. State could be whether a button should be visible or not, it could be the result of that button click and it could also be an Array of users that is coming from an API. State can live in different places throughout our entire application. Some state is very specific to a certain component where other state might be shared in different parts of our application. One piece of state could be a singleton instance, where a another piece of state could share the limited lifespan of a component that can be destroyed at any time.

This big variety of what state could be, how long it lives and where it comes from results in complexity that we need to manage.

What is state management?

State management is the concept of adding, updating, removing and reading pieces of state in an application. When we have deeply nested data structures and we want to update a specific part deep down in the tree, it might become complex. In that case we have state management libraries that contain a Store which helps us with state management to get rid of that complexity. A quick note, we have to be careful that these libraries don’t add complexity by overusing them.

Reactive state

Combining state management together with reactive programming can be a really nice way to develop single-page-applications. Whether our focus lies on Angular, Vue or React, combining these two principles will result in more predictable applications.

Now what has state to do with reactive programming? A piece of state can change over time, so in a way we are waiting for new state changes. That makes it asynchronous.

Let’s take this example for instance:

// false------true-----false---true...
sidebarCollapsed$ = this.state.sidebarCollapsed$

The sidebarCollapsed$ stream starts out with false, later on it becomes true and so on. This stream keeps on living. In Angular this state can be consumed with the async pipe as easy as:

<my-awesome-sidebar *ngIf="sidebarCollapsed$|async">
</my-awesome-sidebar>

The async pipe will subscribe to the sidebarCollapsed$ pass it to the component, mark it for check and will automatically unsubscribe when the component gets destroyed. Keeping state in an observer pattern is nice because we can subscribe to the changes. Oh, and did I mention it plays super nice with Angular?

We can either use a BehaviorSubject or state management frameworks that support Observables. Here are some really great ones with Observable support:

Immutability and Unidirectional data flow

Before we dive deeper in state, there are 2 important principles that we should follow when managing state. The first principle is immutability, which means that we should never mutate data directly without creating a new reference of that object. If we mutate data directly, our application becomes unpredictable and it’s really hard to trace bugs. When we work in an immutable fashion we can also take advantage of performance strategies like the ChangeDetection.OnPush from Angular or React its PureComponent.

When we use typescript we can enforce the typescript compiler to complain when we mutate data

type Foo = {
    readonly bar: string; 
    readonly baz: number; 
}

let first = {bar: 'test', baz: 1};
first.bar = 'test2'; // compilation error
first = {...first, bar: 'test2'}; // success

In the previous example we have overwritten the first instance with an entire new instance that has an updated bar property.

Arrays can be handled like this:

let arr = ['Brecht', 'Kwinten'];
arr.push('John'); // BAD: arr is mutated
arr = [...arr, 'John']; // Good, arr gets new reference

the Array prototype also has some great helper functions that we can use to enforce immutability like map() and filter() but this is not in scope for this article.

The second principle is Unidirectional data flow. In a nutshell, this means that we should never use two-way data binding on state. It is the absolute owner of that specific piece of state that is in charge of updating it (immutable of course).

Both of these principles are highly enforced by the Redux pattern.

What kind of states are there?

Router state

Often forgotten, but one of the most important pieces of state a web application can have. Putting state in the route gives us the following advantages:

  • We can use the browser navigation buttons
  • We can bookmark the state
  • We can can copy and paste the url with the state to other users
  • We don’t have to manage it, it’s always there in the route

Tip: Instead of handling modals with a userDetailModalVisible property, why not enjoy all the benefits mentioned above and bind it to a users/:userId route? Using a child router-outlet in Angular makes this a piece of cake as we can see in this snippet.

<table>
<!--contains users -->
</table>
<router-outlet>
<!-- user detail modal rendered in here -->
</router-outlet>

Component state

Every component could contain state. That state could be shared with its dumb components or could be used in the component itself. Eg: When an ItemComponent has a property selectedItems which is an array of ids, and that array is never used in other components (that aren’t children of that component), we can consider it component state. It belongs to that component, therefore the component should be responsible for it. Child components can consume that state but should never mutate it. Those components can notify their parent that is responsible for it, which could update it in an immutable way. For more information about smart and dumb components look here.

Personally, I try to avoid state management frameworks for managing component state because it’s the responsibility of that component to manage that state. There are however good reasons to use state management frameworks to manage component state:

If the state management of the component becomes a bit too complex and we don’t want to use a state management framework just yet, we could use a state reducer in the component itself.

Persisted state

Persisted state, is state that is being remembered when the user navigates between different pages. This could be whether a sidebar was collapsed or not, or when the user returns to a grid with a lot of filters and he wants them to be remembered and reapplied when he returns. Another example is a wizard with different steps, and every step needs to be persisted so the user can navigate back and forth and the last page is a result of all these steps.

Persisted state is the type of state where we typically use a state management framework for, that being said, if we don’t want to rely on an external dependency we can also manage it in a Angular service which can be a singleton that is shared throughout the entire application. If that service becomes too complex or there is a lot of state to manage, I would consider to put that state into a state management framework.

Shared state

When we are talking about shared state, we are talking about state that needs to be shared between different parts of our application. State that is being shared throughout different smart components. This means that the instance of this piece of state should live on a higher level, than the components that want to consume it.

Shared state can be managed in a state management framework like ReduxNgrxAkitaNgxs and so on, but if that state is small and simple we can also manage it manually. Let’s say that we want an Observable of an Array of countries that we need to share throughout the entire application. In Angular we could have a CountryService that fetches the countries from the API once, and then shares it throughout the entire application. For that we can use the shareReplay operator from RxJS.

export class CountryService {
    ...
    countries$ = this.httpClient.get('countries').pipe(shareReplay(1));
}

Simple right, one line of code?! For this we don’t need a state management framework, although it can also have its benefits. Some developers like to keep all their master data in a Redux store, and that’s fine. Just know that we don’t have to. I like to develop by the KISS principle (Keep ISimple Stupid) as much as possible, so I favor this approach many times. Think about the amount of lines of code we saved by this approach. Beware that every line of code we write, not only needs to be written but also maintained.

Which state needs to be managed?

Now that we know what state is, we have to ask ourselves which state needs to be managed, and where do we manage that state? In a component, singleton service or a framework (Store)?

This is the part where the strong opinions surface. I would suggest to use what works for you and your team and really think about, but here are my personal opinionated guidelines:

  • I try to avoid state management frameworks where possible. RxJS already leverages us with a lot already and I like to think KISS.
  • I try to avoid using state management frameworks to communicate with different parts in my application, I believe state is unrelated to communication.
  • When my component can handle the state and it’s not too complex, I let my component in charge of managing that state.
  • Master data like countries are exposed in a service which uses the shareReplay operator.
  • I don’t put the result of a getById API call into a store if there is no one consuming that state except for the component requesting it
  • I use a facade between my smart components and my store/services to make refactoring easier in the future.

However, there is also a popular opinion out there to put literally everything in the store which has the following advantages:

  • We can see the flow of the code in devtools
  • Consistent pattern
  • We can leverage selectors with memoization
  • Easier for realtime applications
  • Optimistic updates are easier

However, there are a few downsides as well:

  • A gigantic amount of bloat code: Bigger bundle size, more maintenance and dev time. Eg: If we would use the complete Ngrx pattern for the countries$ example we would have to write an: actionactiontypeeffect and a reducer.
  • Tightly coupled to a strong dependency that is hard to get rid of in the future
  • Generally more complex
  • The user his screen can get out of sync with the backend
  • Cache invalidation: if we add a currentUserToEdit in the store, we have to get it out when we navigate away
  • We can’t use the async pipe to cancel pending XHR requests
  • We create a distributed monolith of some sort

More to read

Python | Working with Azure

First Step: Hello World Sample

The following steps at borrowed from the quick start tutorial.

Download sample repository

$ git clone https://github.com/Azure-Samples/python-docs-hello-world
$ cd python-docs-hello-world

Create virtual environment

$ python3 -m venv venv
$ source venv/bin/activate
$ pip install -r requirements.txt
$ export FLASK_APP=application.py
$ flask run

Login zu Azure

$ az login

Deploy to App Service

$ az webapp up --sku F1 -n azure-toolbox-flask-demo -l westeurope
webapp azure-toolbox-flask-demo doesn't exist
Creating Resource group 'xx_xx_Linux_westeurope' ...
Resource group creation complete
Creating AppServicePlan 'xx_asp_Linux_westeurope_0' ...
Creating webapp 'flask-demo' ...
Configuring default logging for the app, if not already enabled
Creating zip with contents of dir .../Working-with_Python ...
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
You can launch the app at http://via-internet-flask-demo.azurewebsites.net
{
  "URL": "http:/azure-toolbox-flask-demo.azurewebsites.net",
  "appserviceplan": "xx_asp_Linux_westeurope_0",
  "location": "westeurope",
  "name": "azure-toolbox--flask-demo",
  "os": "Linux",
  "resourcegroup": "xx_xx_Linux_westeurope",
  "runtime_version": "python|3.7",
  "runtime_version_detected": "-",
  "sku": "FREE",
  "src_path": ".../Working-with_Python"
}

Create Django App with PostgreSQL

Installation PostgreSQL on Mac OS

$ brew install postgres
==> Installing dependencies for postgresql: krb5
==> Installing postgresql dependency: krb5
...
==> Installing postgresql
...
==> Caveats
==> krb5
krb5 is keg-only, which means it was not symlinked into /usr/local, because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have krb5 first in your PATH run:
  echo 'export PATH="/usr/local/opt/krb5/bin:$PATH"' >> ~/.bash_profile
  echo 'export PATH="/usr/local/opt/krb5/sbin:$PATH"' >> ~/.bash_profile

For compilers to find krb5 you may need to set:
  export LDFLAGS="-L/usr/local/opt/krb5/lib"
  export CPPFLAGS="-I/usr/local/opt/krb5/include"

For pkg-config to find krb5 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/krb5/lib/pkgconfig"

==> postgresql
To migrate existing data from a previous major version of PostgreSQL run:
  brew postgresql-upgrade-database

To have launchd start postgresql now and restart at login:
  brew services start postgresql
Or, if you don't want/need a background service you can just run:
  pg_ctl -D /usr/local/var/postgres start

Set user and passwords for postgres database

Create database and user for django app

$ psql postgres
psql (12.1)
Type "help" for help.

postgres=# CREATE DATABASE pollsdb;
CREATE DATABASE
postgres=# CREATE USER manager WITH PASSWORD '########';
CREATE ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE pollsdb TO manager;
GRANT

Download sample repository

$ git clone https://github.com/Azure-Samples/djangoapp.git
$ cd djangoapp

Create virtual environment

$ python3 -m venv venv
$ source venv/bin/activate
$ pip install -r requirements.txt
$ cat env.sh
export DBHOST="localhost"
export DBUSER="manager"
export DBNAME="pollsdb"
export DBPASS="supersecretpass"
$ . env.sh
$ python manage.py  makemigrations
No changes detected
$ python manage.py  migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying polls.0001_initial... OK
  Applying sessions.0001_initial... OK
 $ python manage.py createsuperuser
Username (leave blank to use 'user'): admin
Email address: admin@localhost
Password:
Password (again):
Superuser created successfully.

Run server and acccess web page at http://127.0.0.1:8000/

$ python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
January 25, 2020 - 16:42:14
Django version 2.1.2, using settings 'azuresite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[25/Jan/2020 16:42:26] "GET / HTTP/1.1" 200 111
[25/Jan/2020 16:42:26] "GET /static/polls/style.css HTTP/1.1" 200 27
Not Found: /favicon.ico
[25/Jan/2020 16:42:26] "GET /favicon.ico HTTP/1.1" 404 2688

Login zu Azure

$ az login

Deploy to App Service

$ az webapp up --sku F1 -n azure-toolbox-django-demo -l westeurope
webapp azure-toolbox-django-demo doesn't exist
Creating Resource group 'xx_xx_Linux_westeurope' ...
Resource group creation complete
Creating AppServicePlan 'xx_asp_Linux_westeurope_0' ...
Creating webapp 'flask-demo' ...
Configuring default logging for the app, if not already enabled
Creating zip with contents of dir .../Working-with_Django ...
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
You can launch the app at http://via-internet-django-demo.azurewebsites.net
{
  "URL": "http:/azure-toolbox-django-demo.azurewebsites.net",
  "appserviceplan": "xx_asp_Linux_westeurope_0",
  "location": "westeurope",
  "name": "azure-toolbox--django-demo",
  "os": "Linux",
  "resourcegroup": "xx_xx_Linux_westeurope",
  "runtime_version": "python|3.7",
  "runtime_version_detected": "-",
  "sku": "FREE",
  "src_path": ".../Working-with_Django"
}

Additional Reading

Installation

Here is the documentation from Microsoft.

Mac OS

Install with Homebrew

$ brew update && brew install azure-cli
$ az login

Ionic | Working with p5.js

Introduction

p5.js is a JavaScript library for creative coding, with a focus on making coding accessible and inclusive for artists, designers, educators, beginners, and anyone else.

In this Post, you will learn how to create an ionic app using p5.js to create an amazing demo of the Lissajous Curve (de/en).

If you want to learn more about this amazing graphs, take a look at some examples at codepen.io. Some amazing examples are here, here and here.

As always, you will find the final project on Github.

Preparation

You will need to install Node.Js, Ionic and p5.js.

First step is, to install Node.Js. I will refer to the installation pages on Node JS.

After installing Node.js, you will also have the Node Package manager, npm. We will use this tool to install the other requirements.

Install Ionic

$ npm -g install ionic

Create base app structure

$ ionic start working-with-p5js blank --type angular
$ cd working-with-p5js

Add p5.js to the ionic app

$ npm install --save-dev p5

Test your base app

$ ionic serve

Starting with p5.js

Add the base components of p5.js to our app.

Modify home.page.html

<ion-header>
	<ion-toolbar>
		<ion-title>Working with p5.js</ion-title>
	</ion-toolbar>
</ion-header>

<ion-content>
	<div id="canvasContainer" class="canvas-container"></div>
</ion-content>

Modify home.page.ts

import { Component, OnInit, ElementRef } from '@angular/core';

import * as p5 from 'p5';

@Component({
	selector: 'app-home',
	templateUrl: 'home.page.html',
	styleUrls: [ 'home.page.scss' ],
})
export class HomePage implements OnInit {

  curve: any;
  canvasSizeX = 200;
  canvasSizeY = 200;

  constructor(private el: ElementRef) { }

  ngOnInit() {
    const p5obj = new p5(p => {
      p.setup = () => { this.setup(p); };
      p.draw = () => { this.draw(p); };
    }, this.el.nativeElement);
  }

  setup(p) {
    const c = document.querySelector('#canvasContainer');
    p
      .createCanvas(this.canvasSizeX, this.canvasSizeY)
      .parent(c);
  }

  draw(p) {
    p.background(220);
  }
}

And the result ist your frist p5.js graphic:

Next, we will grab an example from p5.js: form-regular-polygon

Modify home.page.ts

We modify the draw function and add a required polygon function.

import { Component, OnInit, ElementRef } from '@angular/core';

import * as p5 from 'p5';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {

  curve: any;
  canvasSizeX = 720;
  canvasSizeY = 400;

  private ID = 'HomePage';
  log(func, line = '') {
    console.log(this.ID + '::' + func + '|' + line);
  }

  constructor(
    private el: ElementRef
  ) {
    this.log('constructor');
  }

  ngOnInit() {
    this.log('ngOnInit');

    const p5obj = new p5(p => {
      p.setup = () => {
        this.setup(p);
      };

      p.draw = () => {
        this.draw(p);
      };
    }, this.el.nativeElement);
  }

  setup(p) {
    this.log('setup');

    const c = document.querySelector('#canvasContainer');
    p
      .createCanvas(this.canvasSizeX, this.canvasSizeY)
      .parent(c);
  }

  polygon(p, x, y, radius, npoints, color) {
    const angle = p.TWO_PI / npoints;
    p.beginShape();

    p.fill(color);
    for (let a = 0; a < p.TWO_PI; a += angle) {
      const sx = x + Math.cos(a) * radius;
      const sy = y + Math.sin(a) * radius;
      p.vertex(sx, sy);
    }
    p.endShape(p.CLOSE);
  }

  draw_figure(p, scaleX, scaleY, divisor, radius, npoints, color) {
    p.push();
    p.translate(this.canvasSizeX * scaleX, this.canvasSizeY * scaleY);
    p.rotate(p.frameCount / divisor);
    this.polygon(p, 0, 0, radius, npoints, color);
    p.pop();
  }

  draw(p) {
    p.
      background('white');

    this.draw_figure(p, 0.2, 0.5, 200.0, 82, 3, 'red');
    this.draw_figure(p, 0.5, 0.5, 50.0, 80, 20, 'blue');
    this.draw_figure(p, 0.8, 0.5, -100.0, 70, 7, 'green');
  }
}

And we get a nice litte animation:

Additional Resources and Reading

Websites

Copyright © 2024 | Powered by WordPress | Aasta Blog theme by ThemeArile