Skip to content

Commit

Permalink
Testikus example plugin and juno plugin generator (#1110)
Browse files Browse the repository at this point in the history
* first use of juno-ui-components

* [testikus] add juno variant to SearchField

* [testikus] change juno-ui-component release to latest. add tailwind, postcss, autoprefixer

* [testikus] clean up usage of juno-ui-components in example

* [testikus] item fixes

* [testikus] action buttons in item. searchbar in toolbar. show dialog with DataGrid.

* [testikus] use Badge for item uid

* [testikus] remove obsolete class name

* add example for os proxy requests to testikus

* finish the generator for juno plugins

* [testikus] prettify catalog/show. update juno-ui-components.

Co-authored-by: d064310 <[email protected]>
  • Loading branch information
edda and andypf authored Jul 28, 2022
1 parent 568f4de commit 18cf1cc
Show file tree
Hide file tree
Showing 80 changed files with 2,468 additions and 68 deletions.
12 changes: 11 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ PATH
specs:
shared_filesystem_storage (0.0.1)

PATH
remote: plugins/testikus
specs:
testikus (0.1.0)

PATH
remote: plugins/tools
specs:
Expand Down Expand Up @@ -393,12 +398,16 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2022.0105)
mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.16.1)
multi_json (1.15.0)
net-ssh (6.1.0)
netaddr (2.0.4)
netrc (0.11.0)
nio4r (2.5.8)
nokogiri (1.13.6)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.6-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.13.6-x86_64-linux)
Expand Down Expand Up @@ -634,10 +643,11 @@ DEPENDENCIES
spinners
spring
sqlite3
testikus!
tools!
unf (>= 0.2.0beta2)
web-console (~> 3.0)
webconsole!

BUNDLED WITH
2.3.15
2.3.18
16 changes: 16 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,23 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception

helper_method :modal?, :plugin_name, :modal_size
helper_method :release_state

# Overwrite this method in your controller if you want to set the release
# state of your plugin to a different value. A tag will be displayed in
# the main toolbar next to the page header
# DON'T OVERWRITE THE VALUE HERE IN THE DASHBOARD CONTROLLER
# Possible values:
# ----------------
# "public_release" (plugin is properly live and works, default)
# "experimental" (for plugins that barely work or don't work at all)
# "tech_preview" (early preview for a new feature that probably still
# has several bugs)
# "beta" (if it's almost ready for public release)
def release_state
"public_release"
end

# catch all api errors and render exception page

rescue_from 'Elektron::Errors::Request' do |exception|
Expand Down
17 changes: 1 addition & 16 deletions app/controllers/dashboard_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,22 +198,7 @@ def two_factor_required?

protected

helper_method :release_state

# Overwrite this method in your controller if you want to set the release
# state of your plugin to a different value. A tag will be displayed in
# the main toolbar next to the page header
# DON'T OVERWRITE THE VALUE HERE IN THE DASHBOARD CONTROLLER
# Possible values:
# ----------------
# "public_release" (plugin is properly live and works, default)
# "experimental" (for plugins that barely work or don't work at all)
# "tech_preview" (early preview for a new feature that probably still
# has several bugs)
# "beta" (if it's almost ready for public release)
def release_state
"public_release"
end


def show_beta?
params[:betafeatures] == "showme"
Expand Down
56 changes: 36 additions & 20 deletions app/javascript/lib/components/search_field.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Popover, OverlayTrigger } from 'react-bootstrap';
import { SearchInput } from "juno-ui-components"
import React from 'react';

let counter = 0;

Expand Down Expand Up @@ -38,39 +40,53 @@ export class SearchField extends React.Component {


render() {
const variant = this.props.variant
const empty = this.state.searchTerm.trim().length==0
const showSearchIcon = (this.props.searchIcon != false)
let iconClassName = empty ? (showSearchIcon ? 'fa fa-search' : '') : 'fa fa-times-circle'
if (this.props.isFetching) iconClassName = 'spinner'

return (
<React.Fragment>
<div className='has-feedback has-feedback-searchable'>
<input
data-test="search"
type="text"
className="form-control"
{ variant === "juno" ?
<SearchInput
value={this.state.searchTerm}
placeholder={this.props.placeholder}
disabled={this.props.disabled === true}
onChange={this.onChangeTerm}
disabled={this.props.disabled==true}
onClear={(e) => this.reset(e)}
/>
<span
className={`form-control-feedback ${!empty && 'not-empty'}`}
onClick={(e) => iconClassName!='spinner' && !empty && this.reset(e)}>
<i className={iconClassName}/>
</span>
</div>
{this.props.text &&
<div className="has-feedback-help">
<OverlayTrigger trigger="click" placement="top" rootClose overlay={this.infoText}>
<a className='help-link' href='#' onClick={() => null}>
<i className="fa fa-question-circle"></i>
</a>
</OverlayTrigger>
</div>
:
<React.Fragment>
<div className='has-feedback has-feedback-searchable'>
<input
data-test="search"
type="text"
className="form-control"
value={this.state.searchTerm}
placeholder={this.props.placeholder}
onChange={this.onChangeTerm}
disabled={this.props.disabled==true}
/>
<span
className={`form-control-feedback ${!empty && 'not-empty'}`}
onClick={(e) => iconClassName!='spinner' && !empty && this.reset(e)}>
<i className={iconClassName}/>
</span>
</div>
{this.props.text &&
<div className="has-feedback-help">
<OverlayTrigger trigger="click" placement="top" rootClose overlay={this.infoText}>
<a className='help-link' href='#' onClick={() => null}>
<i className="fa fa-question-circle"></i>
</a>
</OverlayTrigger>
</div>
}
</React.Fragment>
}
</React.Fragment>

)
}
}
11 changes: 10 additions & 1 deletion app/javascript/lib/widget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ class Widget {

render(container) {
// const Container = React.createElement(container, this.config.params)

for (let reactContainer of this.reactContainers) {
let dataset = getDataset(reactContainer)
let wrappedComponent = React.createElement(
Expand Down Expand Up @@ -192,3 +191,13 @@ export const getContainerFromCurrentScript = (widgetName) => {
scriptParams,
}
}

export const widgetBasePath = (widgetPathName) => {
const regex = new RegExp(`^(.*/${widgetPathName}).*$`)
const baseNameMatch = window.location.pathname.match(regex)
if (baseNameMatch) return baseNameMatch[1]
let baseNamePath = ""
if (window.scopedDomainFid) baseNamePath += "/" + window.scopedDomainFid
if (window.scopedProjectFid) baseNamePath += "/" + window.scopedProjectFid
return baseNamePath
}
11 changes: 11 additions & 0 deletions bin/create-db
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

# Spring:
# (Note: using gem "spring", git: "..." won't work and is not a supported way of using Spring.)
# So we have to disable spring for generate tasks!

full_path=$(realpath "$0")
dir=$(dirname "$full_path")

DISABLE_SPRING=1 "$dir"/rails db:create
DISABLE_SPRING=1 "$dir"/rails db:migrate
2 changes: 1 addition & 1 deletion bin/generate
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
full_path=$(realpath "$0")
dir=$(dirname "$full_path")

DISABLE_SPRING=1 "$dir"/rails generate "$@"
DISABLE_SPRING=1 "$dir"/rails generate dashboard_plugin "$@" && bundle
1 change: 1 addition & 0 deletions esbuild/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ require("esbuild")
// map global this to window
define: { this: "window" },
allowOverwrite: true,
loader: { ".css": "text" },
})
.then((result) => {
if (watch) console.log("watching...")
Expand Down
9 changes: 7 additions & 2 deletions lib/generators/dashboard_plugin_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
require_relative './plugin_skeleton_generator'
require_relative './default_plugin_generator'
require_relative './react_plugin_generator'
require_relative './juno_plugin_generator'

class DashboardPluginGenerator < Rails::Generators::NamedBase
PLUGINS_PATH = 'plugins'

source_root File.expand_path('../templates', __FILE__)
class_option :mountable, type: :boolean, default: true, description: 'Generate mountable isolated application'
class_option :service_layer, type: :boolean, default: false, description: 'Generate service layer (app/services/service_layer/)'
class_option :react, type: :boolean, default: false, description: 'Generate plugin using react/redux js-lib'
class_option :react, type: :boolean, default: false, description: 'Generate plugin using react'
class_option :juno, type: :boolean, default: true, description: 'Generate plugin using react and juno ui components'

def start
PluginSkeletonGenerator.new(self, PLUGINS_PATH).run

if options.react?
if options.juno?
JunoPluginGenerator.new(self,PLUGINS_PATH).run
elsif options.react?
ReactPluginGenerator.new(self, PLUGINS_PATH).run
else
DefaultPluginGenerator.new(self, PLUGINS_PATH).run
Expand All @@ -28,6 +32,7 @@ def cleanup

gsub_file(file, '%{PLUGIN_NAME}', name)
gsub_file(file, '%{PLUGIN_NAME_CAMELIZE}', name.camelize)
gsub_file(file, '%{PLUGIN_NAME_HUMANIZE}', name.humanize)
gsub_file(file, /\n[\n]+/, "\n\n")
end

Expand Down
47 changes: 47 additions & 0 deletions lib/generators/juno_plugin_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

class JunoPluginGenerator
extend Forwardable
def_delegators :@context, :options, :copy_file, :remove_file, :gsub_file,
:create_file, :name, :remove_dir, :directory, :source_paths,
:append_to_file
attr_reader :plugin_path

def initialize(context, plugin_path)
@context = context
@plugin_path = plugin_path
end

def run
add_packs
return unless options.mountable?

modify_app
add_routes
update_assets
end

private

def update_assets
remove_file "#{plugin_path}/#{name}/app/assets/stylesheets/#{name}/application.css"
remove_dir "#{plugin_path}/#{name}/app/assets/javascripts"
copy_file 'juno/app/assets/stylesheets/_application.scss', "#{plugin_path}/#{name}/app/assets/stylesheets/#{name}/_application.scss"
end

def add_packs
directory 'juno/app/javascript', "#{plugin_path}/#{name}/app/javascript"
end

def modify_app
remove_dir "#{plugin_path}/#{name}/app/controllers"
directory 'juno/app/controllers', "#{plugin_path}/#{name}/app/controllers/#{name}"
remove_dir "#{plugin_path}/#{name}/app/views"
directory 'juno/app/views', "#{plugin_path}/#{name}/app/views/#{name}"
end

def add_routes
remove_file "#{plugin_path}/#{name}/config/routes.rb"
copy_file 'juno/config/routes.rb', "#{plugin_path}/#{name}/config/routes.rb"
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/****************************************************
We are using SASS!
DO NOT USE require, require_tree, and require_self.
***************************************************
Instead require further sass files by calling the "@import" directive.
e.g. @import "mixins";
FYI:
The core app automatically namespaces a plugin's css with the plugin name. I.e. it encapsulates a plugin's css in a class with the same name as plugin (e.g. a plugin with name "myplugin" gets surrounded with css class ".myplugin").
This means that in the compiled css e.g. .test { color: #f00; } becomes .myplugin .test { color: #f00; }
The core app also ensures that the content div surrounding the plugin views gets a css class with the name of current plugin.
This way we ensure that your styles take effect only inside the content of your plugin and don't accidentally overwrite styles defined elsewhere (either in the core or in another plugin).
***************************************************
IMPORTANT
---------------------------------------------------
1) The namespacing happens automatically. There is no special action required from the plugin author. The only thing you need to pay attention to is that you write styles only for elements in the context of your plugin's views.
2) Make sure all your scss files are partials (i.e. the file name starts with an underscore, e.g. "_application.scss"). Otherwise the base imports in the main stylesheet won't be available in your engine stylesheets!
***************************************************
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

module %{PLUGIN_NAME_CAMELIZE}
class ApplicationController < AjaxController
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module %{PLUGIN_NAME_CAMELIZE}
class EntriesController < AjaxController
def index
render json: [
{ id: SecureRandom.uuid, name: 'Entry1', description: 'Test Entry 1'},
{ id: SecureRandom.uuid, name: 'Entry2', description: 'Test Entry 2'},
{ id: SecureRandom.uuid, name: 'Entry3', description: 'Test Entry 3'}
]
end

def create
render json: {
id: SecureRandom.uuid,
name: params[:entry][:name],
description: params[:entry][:description]
}
end

def update
render json: {
id: params[:id],
name: params[:entry][:name],
description: params[:entry][:description]
}
end

def destroy
head :no_content
end
end
end
4 changes: 4 additions & 0 deletions lib/generators/templates/juno/app/javascript/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This is always executed on page load.
$(document).ready(function () {
// ...
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createAjaxHelper } from "lib/ajax_helper"
import { widgetBasePath } from "lib/widget"

const baseURL = widgetBasePath("%{PLUGIN_NAME}")
export default createAjaxHelper({ baseURL })
Loading

0 comments on commit 18cf1cc

Please sign in to comment.