init alerting
This commit is contained in:
parent
5a7f978fea
commit
05300a6a01
|
@ -8,6 +8,7 @@ import (
|
||||||
"infini.sh/search-center/api/index_management"
|
"infini.sh/search-center/api/index_management"
|
||||||
"infini.sh/search-center/config"
|
"infini.sh/search-center/config"
|
||||||
"path"
|
"path"
|
||||||
|
"infini.sh/search-center/service/alerting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(cfg *config.AppConfig) {
|
func Init(cfg *config.AppConfig) {
|
||||||
|
@ -36,6 +37,13 @@ func Init(cfg *config.AppConfig) {
|
||||||
ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix, "index/:index"), handler.HandleDeleteIndexAction)
|
ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix, "index/:index"), handler.HandleDeleteIndexAction)
|
||||||
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "index/:index"), handler.HandleCreateIndexAction)
|
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "index/:index"), handler.HandleCreateIndexAction)
|
||||||
|
|
||||||
|
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/alerts", alerting.GetAlerts)
|
||||||
|
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/monitors/_search", alerting.Search)
|
||||||
|
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/_indices", alerting.GetIndices)
|
||||||
|
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/_aliases", alerting.GetAliases)
|
||||||
|
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/_mappings", alerting.GetMappings)
|
||||||
|
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/_plugins", alerting.GetPlugins)
|
||||||
|
|
||||||
|
|
||||||
task.RegisterScheduleTask(task.ScheduleTask{
|
task.RegisterScheduleTask(task.ScheduleTask{
|
||||||
Description: "sync reindex task result",
|
Description: "sync reindex task result",
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
package alerting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
httprouter "infini.sh/framework/core/api/router"
|
||||||
|
"infini.sh/framework/core/elastic"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getQueryParam(req *http.Request, key string, or ...string) string {
|
||||||
|
query := req.URL.Query()
|
||||||
|
val := query.Get(key)
|
||||||
|
if val == "" && len(or)>0 {
|
||||||
|
return or[0]
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAlerts (w http.ResponseWriter, req *http.Request, ps httprouter.Params){
|
||||||
|
id := ps.ByName("id")
|
||||||
|
conf := elastic.GetConfig(id)
|
||||||
|
if conf == nil {
|
||||||
|
writeError(w, errors.New("cluster not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
from = getQueryParam(req, "from", "0")
|
||||||
|
size = getQueryParam(req, "size", "20")
|
||||||
|
search = getQueryParam(req, "search")
|
||||||
|
sortDirection = getQueryParam(req, "sortDirection", "desc")
|
||||||
|
sortField = getQueryParam(req, "sortField", "start_time")
|
||||||
|
severityLevel = getQueryParam(req, "severityLevel", "ALL")
|
||||||
|
alertState = getQueryParam(req, "alertState", "ALL")
|
||||||
|
//monitorIds = getQueryParam(req, "monitorIds")
|
||||||
|
params = map[string]string{
|
||||||
|
"startIndex": from,
|
||||||
|
"size": size,
|
||||||
|
"severityLevel": severityLevel,
|
||||||
|
"alertState": alertState,
|
||||||
|
"searchString": search,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
switch sortField {
|
||||||
|
case "monitor_name", "trigger_name":
|
||||||
|
params["sortString"] = fmt.Sprintf(`%s.keyword`, sortField)
|
||||||
|
params["sortOrder"] = sortDirection
|
||||||
|
case "start_time":
|
||||||
|
params["sortString"] = sortField
|
||||||
|
params["sortOrder"] = sortDirection
|
||||||
|
case "end_time":
|
||||||
|
params["sortString"] = sortField
|
||||||
|
params["sortOrder"] = sortDirection
|
||||||
|
params["missing"] = "_first"
|
||||||
|
if sortDirection == "asc" {
|
||||||
|
params["missing"] = "_last"
|
||||||
|
}
|
||||||
|
case "acknowledged_time":
|
||||||
|
params["sortString"] = sortField
|
||||||
|
params["sortOrder"] = sortDirection
|
||||||
|
params["missing"] = "_last"
|
||||||
|
}
|
||||||
|
|
||||||
|
if clearSearch := strings.TrimSpace(search); clearSearch != ""{
|
||||||
|
searches := strings.Split(clearSearch, " ")
|
||||||
|
clearSearch = strings.Join(searches, "* *")
|
||||||
|
params["searchString"] = fmt.Sprintf("*%s*", clearSearch)
|
||||||
|
}
|
||||||
|
reqUrl := conf.Endpoint + "/_opendistro/_alerting/monitors/alerts"
|
||||||
|
|
||||||
|
res, err := doRequest(reqUrl, http.MethodGet, params, nil)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var alertRes = AlertResponse{}
|
||||||
|
err = decodeJSON(res.Body, &alertRes)
|
||||||
|
defer res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var alerts = []IfaceMap{}
|
||||||
|
for _, hit := range alertRes.Alerts {
|
||||||
|
alert := IfaceMap{
|
||||||
|
"id": hit["alert_id"],
|
||||||
|
}
|
||||||
|
for k, v := range hit {
|
||||||
|
alert[k] = v
|
||||||
|
}
|
||||||
|
alert["version"] = hit["alert_version"]
|
||||||
|
}
|
||||||
|
writeJSON(w, IfaceMap{
|
||||||
|
"ok": true,
|
||||||
|
"alerts": alerts,
|
||||||
|
"totalAlerts": alertRes.TotalAlerts,
|
||||||
|
}, http.StatusOK)
|
||||||
|
|
||||||
|
}
|
||||||
|
func writeError(w http.ResponseWriter, err error) {
|
||||||
|
writeJSON(w, map[string]interface{}{
|
||||||
|
"body": map[string]interface{}{
|
||||||
|
"ok": false,
|
||||||
|
"err": err.Error(),
|
||||||
|
},
|
||||||
|
}, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IfaceMap map[string]interface{}
|
||||||
|
|
||||||
|
type AlertResponse struct {
|
||||||
|
Alerts []IfaceMap `json:"alerts"`
|
||||||
|
TotalAlerts int `json:"totalAlerts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeJSON(reader io.Reader, obj interface{}) error{
|
||||||
|
dec := json.NewDecoder(reader)
|
||||||
|
return dec.Decode(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeJSON(w http.ResponseWriter, data interface{}, statusCode int){
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
w.Header().Set("content-type", "application/json")
|
||||||
|
buf, _ := json.Marshal(data)
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var alertClient = http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRequest(requestUrl string, method string, params map[string]string, body interface{}) (*http.Response, error){
|
||||||
|
var req *http.Request
|
||||||
|
if params != nil && len(params) > 0 {
|
||||||
|
var queryValues = url.Values{}
|
||||||
|
for k, v := range params {
|
||||||
|
queryValues.Set(k, v)
|
||||||
|
}
|
||||||
|
requestUrl += "?"+ queryValues.Encode()
|
||||||
|
}
|
||||||
|
var reader io.Reader
|
||||||
|
if body != nil {
|
||||||
|
switch body.(type) {
|
||||||
|
case string:
|
||||||
|
reader = bytes.NewBufferString(body.(string))
|
||||||
|
case io.Reader:
|
||||||
|
reader = body.(io.Reader)
|
||||||
|
default:
|
||||||
|
rw := &bytes.Buffer{}
|
||||||
|
enc := json.NewEncoder(rw)
|
||||||
|
err := enc.Encode(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reader = rw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req, _ = http.NewRequest(method, requestUrl, reader)
|
||||||
|
req.Header.Set("content-type", "application/json")
|
||||||
|
return alertClient.Do(req)
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
package alerting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
httprouter "infini.sh/framework/core/api/router"
|
||||||
|
"infini.sh/framework/core/elastic"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearchBody struct {
|
||||||
|
Query IfaceMap `json:"query"`
|
||||||
|
Index string `json:"index"`
|
||||||
|
Size int `json:""size`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Search(w http.ResponseWriter, req *http.Request, ps httprouter.Params){
|
||||||
|
id := ps.ByName("id")
|
||||||
|
conf := elastic.GetConfig(id)
|
||||||
|
if conf == nil {
|
||||||
|
writeError(w, errors.New("cluster not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var body = SearchBody{}
|
||||||
|
err := decodeJSON(req.Body, &body)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reqUrl := conf.Endpoint +"/_opendistro/_alerting/monitors/_search"
|
||||||
|
params := map[string]string{
|
||||||
|
"index": body.Index,
|
||||||
|
}
|
||||||
|
body.Query["size"] = body.Size
|
||||||
|
res, err := doRequest(reqUrl, http.MethodPost, params, body.Query)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
var resBody = IfaceMap{}
|
||||||
|
err = decodeJSON(res.Body, &resBody)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, IfaceMap{
|
||||||
|
"body": IfaceMap{
|
||||||
|
"ok": true,
|
||||||
|
"resp": resBody,
|
||||||
|
},
|
||||||
|
}, http.StatusOK)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetIndices(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||||
|
id := ps.ByName("id")
|
||||||
|
conf := elastic.GetConfig(id)
|
||||||
|
if conf == nil {
|
||||||
|
writeError(w, errors.New("cluster not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = struct{
|
||||||
|
Index string `json:"index"`
|
||||||
|
}{}
|
||||||
|
err := decodeJSON(req.Body, &body)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reqUrl := fmt.Sprintf("%s/_cat/indices/%s", conf.Endpoint, body.Index)
|
||||||
|
params := map[string]string{
|
||||||
|
"format": "json",
|
||||||
|
"h": "health,index,status",
|
||||||
|
}
|
||||||
|
res, err := doRequest(reqUrl, http.MethodGet, params, nil)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
var resBody = []IfaceMap{}
|
||||||
|
err = decodeJSON(res.Body, &resBody)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, IfaceMap{
|
||||||
|
"body": IfaceMap{
|
||||||
|
"ok": true,
|
||||||
|
"resp": resBody,
|
||||||
|
},
|
||||||
|
}, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAliases(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
debug.PrintStack()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
id := ps.ByName("id")
|
||||||
|
conf := elastic.GetConfig(id)
|
||||||
|
if conf == nil {
|
||||||
|
writeError(w, errors.New("cluster not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = struct{
|
||||||
|
Alias string `json:"alias"`
|
||||||
|
}{}
|
||||||
|
err := decodeJSON(req.Body, &body)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reqUrl := fmt.Sprintf("%s/_cat/aliases/%s", conf.Endpoint, body.Alias)
|
||||||
|
params := map[string]string{
|
||||||
|
"format": "json",
|
||||||
|
"h": "alias,index",
|
||||||
|
}
|
||||||
|
res, err := doRequest(reqUrl, http.MethodGet, params, nil)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
var resBody = []IfaceMap{}
|
||||||
|
err = decodeJSON(res.Body, &resBody)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, IfaceMap{
|
||||||
|
"body": IfaceMap{
|
||||||
|
"ok": true,
|
||||||
|
"resp": resBody,
|
||||||
|
},
|
||||||
|
}, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMappings(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||||
|
id := ps.ByName("id")
|
||||||
|
conf := elastic.GetConfig(id)
|
||||||
|
if conf == nil {
|
||||||
|
writeError(w, errors.New("cluster not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = struct{
|
||||||
|
Index string `json:"index"`
|
||||||
|
}{}
|
||||||
|
err := decodeJSON(req.Body, &body)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reqUrl := fmt.Sprintf("%s/%s/_mapping", conf.Endpoint, body.Index)
|
||||||
|
res, err := doRequest(reqUrl, http.MethodGet, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
var resBody = IfaceMap{}
|
||||||
|
err = decodeJSON(res.Body, &resBody)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, IfaceMap{
|
||||||
|
"body": IfaceMap{
|
||||||
|
"ok": true,
|
||||||
|
"resp": resBody,
|
||||||
|
},
|
||||||
|
}, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func GetPlugins(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||||
|
id := ps.ByName("id")
|
||||||
|
conf := elastic.GetConfig(id)
|
||||||
|
if conf == nil {
|
||||||
|
writeError(w, errors.New("cluster not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reqUrl := fmt.Sprintf("%s/_cat/plugins", conf.Endpoint)
|
||||||
|
res, err := doRequest(reqUrl, http.MethodGet, map[string]string{
|
||||||
|
"format": "json",
|
||||||
|
"h": "component",
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
var resBody = []IfaceMap{}
|
||||||
|
err = decodeJSON(res.Body, &resBody)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, IfaceMap{
|
||||||
|
"body": IfaceMap{
|
||||||
|
"ok": true,
|
||||||
|
"resp": resBody,
|
||||||
|
},
|
||||||
|
}, http.StatusOK)
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package alerting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
httprouter "infini.sh/framework/core/api/router"
|
||||||
|
"infini.sh/framework/core/elastic"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetMonitor(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||||
|
id := ps.ByName("id")
|
||||||
|
conf := elastic.GetConfig(id)
|
||||||
|
if conf == nil {
|
||||||
|
writeError(w, errors.New("cluster not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mid := ps.ByName("monitorID")
|
||||||
|
// /_opendistro/_alerting/monitors/uiSjqXsBHT9Hsiy5Dq6g
|
||||||
|
reqUrl := fmt.Sprintf("%s/_opendistro/_alerting/monitors/%s", conf.Endpoint, mid)
|
||||||
|
res, err := doRequest(reqUrl, http.MethodGet, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var resBody = IfaceMap{}
|
||||||
|
err = decodeJSON(res.Body, &resBody)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
if _, ok := resBody["monitor"]; !ok {
|
||||||
|
writeJSON(w, IfaceMap{
|
||||||
|
"body": IfaceMap{
|
||||||
|
"ok": false,
|
||||||
|
},
|
||||||
|
}, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
///_opendistro/_alerting/monitors/_search?index=.opendistro-alerting-alert*
|
||||||
|
|
||||||
|
queryDSL := ` {
|
||||||
|
"size": 0,
|
||||||
|
"query"": {
|
||||||
|
"bool": {
|
||||||
|
"must": {
|
||||||
|
"term"": {
|
||||||
|
"monitor_id": "%s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"aggs": {
|
||||||
|
"active_count": {
|
||||||
|
"terms": {
|
||||||
|
"field": "state",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"24_hour_count": {
|
||||||
|
"date_range": {
|
||||||
|
"field": "start_time",
|
||||||
|
"ranges": [{ "from": "now-24h/h" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
queryDSL = fmt.Sprintf(queryDSL, id)
|
||||||
|
reqUrl = fmt.Sprintf("%s/_opendistro/_alerting/monitors/_search", conf.Endpoint)
|
||||||
|
res, err = doRequest(reqUrl, http.MethodPost, map[string]string{
|
||||||
|
"index": ".opendistro-alerting-alert*",
|
||||||
|
}, queryDSL)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchResBody = IfaceMap{}
|
||||||
|
err = decodeJSON(res.Body, &searchResBody)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//dayCount := queryValue(searchResBody, "aggregations.24_hour_count.buckets.0.doc_count", 0)
|
||||||
|
//activeBuckets := queryValue(searchResBody, "aggregations.active_count.buckets",[]interface{}{})
|
||||||
|
writeJSON(w, IfaceMap{
|
||||||
|
"body": IfaceMap{
|
||||||
|
"ok": true,
|
||||||
|
"resp": resBody,
|
||||||
|
},
|
||||||
|
}, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryValue(obj map[string]interface{}, key string, defaultValue interface{}) interface{} {
|
||||||
|
if key == "" {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
idx := strings.Index(key, ".")
|
||||||
|
if idx == -1 {
|
||||||
|
if v, ok := obj[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
ckey := key[0:idx]
|
||||||
|
|
||||||
|
if v, ok := obj[ckey]; ok {
|
||||||
|
if vmap, ok := v.(map[string]interface{}); ok {
|
||||||
|
return queryValue(vmap, key[idx+1:], defaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
|
@ -104,6 +104,17 @@ export default [
|
||||||
component: './DevTool/Console',
|
component: './DevTool/Console',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//alerting
|
||||||
|
{
|
||||||
|
routes:[
|
||||||
|
{ path: '/', redirect: '/' },
|
||||||
|
],
|
||||||
|
path: '/alerting',
|
||||||
|
name: 'alerting',
|
||||||
|
icon: 'alert',
|
||||||
|
component: './Alerting/index',
|
||||||
|
},
|
||||||
|
|
||||||
//data
|
//data
|
||||||
{
|
{
|
||||||
path: '/data',
|
path: '/data',
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"dns": "^0.2.2",
|
"dns": "^0.2.2",
|
||||||
"dva": "^2.4.0",
|
"dva": "^2.4.0",
|
||||||
"enquire-js": "^0.2.1",
|
"enquire-js": "^0.2.1",
|
||||||
|
"formik": "^2.2.9",
|
||||||
"fp-ts": "^2.10.5",
|
"fp-ts": "^2.10.5",
|
||||||
"hash.js": "^1.1.5",
|
"hash.js": "^1.1.5",
|
||||||
"honeycomb-grid": "^3.1.7",
|
"honeycomb-grid": "^3.1.7",
|
||||||
|
@ -63,13 +64,15 @@
|
||||||
"react-json-view": "^1.19.1",
|
"react-json-view": "^1.19.1",
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"react-use": "^17.2.4",
|
"react-use": "^17.2.4",
|
||||||
|
"react-vis": "^1.11.7",
|
||||||
"readline": "^1.3.0",
|
"readline": "^1.3.0",
|
||||||
"repl": "^0.1.3",
|
"repl": "^0.1.3",
|
||||||
"reqwest": "^2.0.5",
|
"reqwest": "^2.0.5",
|
||||||
"rison-node": "^2.1.1",
|
"rison-node": "^2.1.1",
|
||||||
"rxjs": "^7.2.0",
|
"rxjs": "^7.2.0",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"use-query-params": "^1.2.3"
|
"use-query-params": "^1.2.3",
|
||||||
|
"uuidv4": "^6.2.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"antd-pro-merge-less": "^0.1.0",
|
"antd-pro-merge-less": "^0.1.0",
|
||||||
|
|
|
@ -32,6 +32,7 @@ import {
|
||||||
EuiSwitch,
|
EuiSwitch,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import {useState} from 'react';
|
import {useState} from 'react';
|
||||||
|
import {Button, Icon} from 'antd';
|
||||||
|
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
|
@ -140,18 +141,15 @@ export const Header: React.FC<HeaderProps> = ({
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false} >
|
<EuiFlexItem grow={false} >
|
||||||
<EuiFormRow hasEmptyLabelSpace style={{marginBottom:60,marginTop:'auto'}}>
|
<EuiFormRow hasEmptyLabelSpace style={{marginBottom:60,marginTop:'auto'}}>
|
||||||
<EuiButton
|
<Button
|
||||||
fill
|
type="primary"
|
||||||
iconSide="right"
|
|
||||||
iconType="arrowRight"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
goToNextStep(query, viewName)
|
goToNextStep(query, viewName)
|
||||||
}}
|
}}
|
||||||
isDisabled={isNextStepDisabled || viewName.replace(' ', '').length <= 0}
|
disabled={isNextStepDisabled || viewName.replace(' ', '').length <= 0}
|
||||||
data-test-subj="createIndexPatternGoToStep2Button"
|
|
||||||
>
|
>
|
||||||
下一步
|
下一步<Icon type="right"/>
|
||||||
</EuiButton>
|
</Button>
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
|
|
@ -105,7 +105,7 @@ export class IndicesList extends React.Component<IndicesListProps, IndicesListSt
|
||||||
iconSide="right"
|
iconSide="right"
|
||||||
onClick={this.openPerPageControl}
|
onClick={this.openPerPageControl}
|
||||||
>
|
>
|
||||||
{`Rows per page: ${perPage}`}
|
{`每页行数: ${perPage}`}
|
||||||
</EuiButtonEmpty>
|
</EuiButtonEmpty>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } from '@elastic/eui';
|
||||||
|
import {Button} from 'antd';
|
||||||
|
|
||||||
export const ActionButtons = ({
|
export const ActionButtons = ({
|
||||||
goToPreviousStep,
|
goToPreviousStep,
|
||||||
|
@ -32,19 +33,18 @@ export const ActionButtons = ({
|
||||||
}) => (
|
}) => (
|
||||||
<EuiFlexGroup justifyContent="flexEnd">
|
<EuiFlexGroup justifyContent="flexEnd">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButtonEmpty iconType="arrowLeft" onClick={goToPreviousStep}>
|
<Button icon="left" onClick={goToPreviousStep}>
|
||||||
返回
|
返回
|
||||||
</EuiButtonEmpty>
|
</Button>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButton
|
<Button
|
||||||
isDisabled={!submittable}
|
disabled={!submittable}
|
||||||
data-test-subj="createIndexPatternButton"
|
type="primary"
|
||||||
fill
|
|
||||||
onClick={createIndexPattern}
|
onClick={createIndexPattern}
|
||||||
>
|
>
|
||||||
创建视图
|
创建视图
|
||||||
</EuiButton>
|
</Button>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
);
|
);
|
||||||
|
|
|
@ -39,6 +39,10 @@ import { IndexPatternTableItem } from '../types';
|
||||||
import { getIndexPatterns } from '../utils';
|
import { getIndexPatterns } from '../utils';
|
||||||
import {useGlobalContext} from '../../context';
|
import {useGlobalContext} from '../../context';
|
||||||
import { IndexPattern, IndexPatternField } from '../../import';
|
import { IndexPattern, IndexPatternField } from '../../import';
|
||||||
|
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||||
|
import styles from '@/pages/System/Cluster/step.less';
|
||||||
|
import clusterBg from '@/assets/cluster_bg.png';
|
||||||
|
|
||||||
|
|
||||||
export interface EditIndexPatternProps extends RouteComponentProps {
|
export interface EditIndexPatternProps extends RouteComponentProps {
|
||||||
indexPattern: IndexPattern;
|
indexPattern: IndexPattern;
|
||||||
|
@ -152,16 +156,43 @@ export const EditIndexPattern = withRouter(
|
||||||
|
|
||||||
const showTagsSection = Boolean(indexPattern.timeFieldName || (tags && tags.length > 0));
|
const showTagsSection = Boolean(indexPattern.timeFieldName || (tags && tags.length > 0));
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<div className={styles.pageHeaderContent}>
|
||||||
|
<EuiText>
|
||||||
|
<p>
|
||||||
|
当前页面列出匹配 <strong>{indexPattern.title}</strong> 索引的所有字段,字段类型为 Elasticsearch 定义类型。 若需要更改类型,请使用 Elasticsearch{' '}
|
||||||
|
<EuiLink
|
||||||
|
href="http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html"
|
||||||
|
target="_blank"
|
||||||
|
external
|
||||||
|
>
|
||||||
|
{mappingAPILink}
|
||||||
|
</EuiLink>
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const extraContent = (
|
||||||
|
<div className={styles.extraImg}>
|
||||||
|
<img
|
||||||
|
alt="数据视图"
|
||||||
|
src={clusterBg}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<PageHeaderWrapper title={indexPattern.viewName} content={content} extraContent={extraContent}>
|
||||||
<EuiPanel paddingSize={'l'}>
|
<EuiPanel paddingSize={'l'}>
|
||||||
<div data-test-subj="editIndexPattern" role="region" aria-label={headingAriaLabel}>
|
<div data-test-subj="editIndexPattern" role="region" aria-label={headingAriaLabel}>
|
||||||
<IndexHeader
|
{/* <IndexHeader
|
||||||
indexPattern={indexPattern}
|
indexPattern={indexPattern}
|
||||||
setDefault={setDefaultPattern}
|
setDefault={setDefaultPattern}
|
||||||
refreshFields={refreshFields}
|
refreshFields={refreshFields}
|
||||||
deleteIndexPatternClick={removePattern}
|
deleteIndexPatternClick={removePattern}
|
||||||
defaultIndex={defaultIndex}
|
defaultIndex={defaultIndex}
|
||||||
/>
|
/> */}
|
||||||
<EuiSpacer size="s" />
|
<EuiSpacer size="s" />
|
||||||
{showTagsSection && (
|
{showTagsSection && (
|
||||||
<EuiFlexGroup wrap>
|
<EuiFlexGroup wrap>
|
||||||
|
@ -208,6 +239,7 @@ export const EditIndexPattern = withRouter(
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</EuiPanel>
|
</EuiPanel>
|
||||||
|
</PageHeaderWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { Table } from './components/table';
|
||||||
import { getFieldFormat } from './lib';
|
import { getFieldFormat } from './lib';
|
||||||
import { IndexedFieldItem } from './types';
|
import { IndexedFieldItem } from './types';
|
||||||
import { IndexPatternField, IndexPattern, IFieldType } from '../../../import';
|
import { IndexPatternField, IndexPattern, IFieldType } from '../../../import';
|
||||||
|
import {EuiContext} from '@elastic/eui';
|
||||||
|
|
||||||
interface IndexedFieldsTableProps {
|
interface IndexedFieldsTableProps {
|
||||||
fields: IndexPatternField[];
|
fields: IndexPatternField[];
|
||||||
|
@ -108,11 +109,18 @@ export class IndexedFieldsTable extends Component<
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<EuiContext i18n={{
|
||||||
|
mapping: {
|
||||||
|
'euiTablePagination.rowsPerPage': '每页行数',
|
||||||
|
'euiTablePagination.rowsPerPageOption': '{rowsPerPage} 行'
|
||||||
|
}
|
||||||
|
}}>
|
||||||
<Table
|
<Table
|
||||||
indexPattern={indexPattern}
|
indexPattern={indexPattern}
|
||||||
items={fields}
|
items={fields}
|
||||||
editField={(field) => this.props.helpers.redirectToRoute(field)}
|
editField={(field) => this.props.helpers.redirectToRoute(field)}
|
||||||
/>
|
/>
|
||||||
|
</EuiContext>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@ export default {
|
||||||
'component.noticeIcon.empty': 'No notifications',
|
'component.noticeIcon.empty': 'No notifications',
|
||||||
'menu.home': 'Home',
|
'menu.home': 'Home',
|
||||||
'menu.devtool': 'CONSOLE',
|
'menu.devtool': 'CONSOLE',
|
||||||
|
'menu.alerting': 'AERTING',
|
||||||
|
|
||||||
'menu.cluster': 'CLUSTER',
|
'menu.cluster': 'CLUSTER',
|
||||||
'menu.cluster.overview': 'OVERVIEW',
|
'menu.cluster.overview': 'OVERVIEW',
|
||||||
|
|
|
@ -92,6 +92,7 @@ export default {
|
||||||
|
|
||||||
'menu.home': '首页',
|
'menu.home': '首页',
|
||||||
'menu.devtool': '开发工具',
|
'menu.devtool': '开发工具',
|
||||||
|
'menu.alerting': '告警管理',
|
||||||
|
|
||||||
'menu.cluster': '集群管理',
|
'menu.cluster': '集群管理',
|
||||||
'menu.cluster.overview': '概览',
|
'menu.cluster.overview': '概览',
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { HashRouter as Router, Route } from 'react-router-dom';
|
||||||
|
|
||||||
|
import 'react-vis/dist/style.css';
|
||||||
|
// TODO: review the CSS style and migrate the necessary style to SASS, as Less is not supported in Kibana "new platform" anymore
|
||||||
|
// import './less/main.less';
|
||||||
|
import Main from './pages/Main';
|
||||||
|
import { CoreContext } from './utils/CoreContext';
|
||||||
|
|
||||||
|
export function renderApp(coreStart, params) {
|
||||||
|
const isDarkMode = coreStart.uiSettings.get('theme:darkMode') || false;
|
||||||
|
coreStart.chrome.setBreadcrumbs([{ text: 'Alerting' }]); // Set Breadcrumbs for the plugin
|
||||||
|
|
||||||
|
// Load Chart's dark mode CSS
|
||||||
|
if (isDarkMode) {
|
||||||
|
require('@elastic/charts/dist/theme_only_dark.css');
|
||||||
|
} else {
|
||||||
|
require('@elastic/charts/dist/theme_only_light.css');
|
||||||
|
}
|
||||||
|
|
||||||
|
// render react to DOM
|
||||||
|
ReactDOM.render(
|
||||||
|
<Router>
|
||||||
|
<CoreContext.Provider
|
||||||
|
value={{ http: coreStart.http, isDarkMode, notifications: coreStart.notifications }}
|
||||||
|
>
|
||||||
|
<Route render={(props) => <Main title="Alerting" {...props} />} />
|
||||||
|
</CoreContext.Provider>
|
||||||
|
</Router>,
|
||||||
|
params.element
|
||||||
|
);
|
||||||
|
return () => ReactDOM.unmountComponentAtNode(params.element);
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiText } from '@elastic/eui';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
titleText: PropTypes.string,
|
||||||
|
emptyText: PropTypes.string,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
items: PropTypes.array.isRequired,
|
||||||
|
onAdd: PropTypes.func.isRequired,
|
||||||
|
onRemove: PropTypes.func.isRequired,
|
||||||
|
onRenderKeyField: PropTypes.func.isRequired,
|
||||||
|
onRenderValueField: PropTypes.func.isRequired,
|
||||||
|
addButtonText: PropTypes.string,
|
||||||
|
removeButtonText: PropTypes.string,
|
||||||
|
isEnabled: PropTypes.bool,
|
||||||
|
};
|
||||||
|
const defaultProps = {
|
||||||
|
titleText: '',
|
||||||
|
emptyText: 'No attributes found.',
|
||||||
|
addButtonText: 'Add',
|
||||||
|
removeButtonText: 'Remove',
|
||||||
|
isEnabled: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const AttributeEditor = ({
|
||||||
|
titleText,
|
||||||
|
emptyText,
|
||||||
|
name,
|
||||||
|
items,
|
||||||
|
onAdd,
|
||||||
|
onRemove,
|
||||||
|
onRenderKeyField,
|
||||||
|
onRenderValueField,
|
||||||
|
addButtonText,
|
||||||
|
removeButtonText,
|
||||||
|
isEnabled,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<EuiFlexGroup direction="column" alignItems="flexStart" style={{ paddingLeft: '10px' }}>
|
||||||
|
{!_.isEmpty(titleText) ? (
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiText size="xs">{titleText}</EuiText>
|
||||||
|
</EuiFlexItem>
|
||||||
|
) : null}
|
||||||
|
{!_.isEmpty(items) ? (
|
||||||
|
items.map((item, index) => (
|
||||||
|
<EuiFlexItem style={{ marginBottom: 0 }} key={`${name}.${index}.key`}>
|
||||||
|
<EuiFlexGroup alignItems="center">
|
||||||
|
<EuiFlexItem>
|
||||||
|
{onRenderKeyField(`${name}.${index}.key`, index, isEnabled)}
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem>
|
||||||
|
{onRenderValueField(`${name}.${index}.value`, index, isEnabled)}
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButton
|
||||||
|
style={{ marginTop: 10 }}
|
||||||
|
size="s"
|
||||||
|
onClick={e => onRemove(index)}
|
||||||
|
disabled={!isEnabled}
|
||||||
|
>
|
||||||
|
{removeButtonText}
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiFlexItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<EuiFlexItem style={{ marginBottom: 0 }}>
|
||||||
|
<EuiText size="xs"> {emptyText} </EuiText>
|
||||||
|
</EuiFlexItem>
|
||||||
|
)}
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiButton size="s" onClick={onAdd} disabled={!isEnabled}>
|
||||||
|
{addButtonText}
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
AttributeEditor.propTypes = propTypes;
|
||||||
|
AttributeEditor.defaultProps = defaultProps;
|
||||||
|
|
||||||
|
export default AttributeEditor;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import AttributeEditor from './AttributeEditor';
|
||||||
|
|
||||||
|
export default AttributeEditor;
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import queryString from 'query-string';
|
||||||
|
import { EuiBreadcrumbs } from '@elastic/eui';
|
||||||
|
import {
|
||||||
|
APP_PATH,
|
||||||
|
DESTINATION_ACTIONS,
|
||||||
|
MONITOR_ACTIONS,
|
||||||
|
TRIGGER_ACTIONS,
|
||||||
|
} from '../../utils/constants';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
history: PropTypes.object.isRequired,
|
||||||
|
httpClient: PropTypes.object.isRequired,
|
||||||
|
location: PropTypes.object.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Breadcrumbs extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = { breadcrumbs: [] };
|
||||||
|
|
||||||
|
this.getBreadcrumbs = this.getBreadcrumbs.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getBreadcrumbs();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const {
|
||||||
|
location: { pathname: prevPathname, search: prevSearch },
|
||||||
|
} = prevProps;
|
||||||
|
const {
|
||||||
|
location: { pathname, search },
|
||||||
|
} = this.props;
|
||||||
|
if (prevPathname + prevSearch !== pathname + search) {
|
||||||
|
this.getBreadcrumbs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBreadcrumbs() {
|
||||||
|
const {
|
||||||
|
httpClient,
|
||||||
|
history,
|
||||||
|
location: { state: routeState },
|
||||||
|
} = this.props;
|
||||||
|
const rawBreadcrumbs = await getBreadcrumbs(window.location.hash, routeState, httpClient);
|
||||||
|
const breadcrumbs = rawBreadcrumbs.map((breadcrumb) =>
|
||||||
|
createEuiBreadcrumb(breadcrumb, history)
|
||||||
|
);
|
||||||
|
this.setState({ breadcrumbs });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { breadcrumbs } = this.state;
|
||||||
|
return (
|
||||||
|
<EuiBreadcrumbs
|
||||||
|
breadcrumbs={breadcrumbs}
|
||||||
|
responsive={false}
|
||||||
|
truncate={true}
|
||||||
|
style={{ padding: '0px 15px' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Breadcrumbs.propTypes = propTypes;
|
||||||
|
|
||||||
|
export function createEuiBreadcrumb(breadcrumb, history) {
|
||||||
|
const { text, href } = breadcrumb;
|
||||||
|
return {
|
||||||
|
text,
|
||||||
|
href: `#${href}`,
|
||||||
|
onClick: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
history.push(href);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBreadcrumbs(hash, routeState, httpClient) {
|
||||||
|
const routes = parseLocationHash(hash);
|
||||||
|
const asyncBreadcrumbs = await Promise.all(
|
||||||
|
routes.map((route) => getBreadcrumb(route, routeState, httpClient))
|
||||||
|
);
|
||||||
|
const breadcrumbs = _.flatten(asyncBreadcrumbs).filter((breadcrumb) => !!breadcrumb);
|
||||||
|
return breadcrumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseLocationHash(hash) {
|
||||||
|
return hash.split('/').filter((route) => !!route);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBreadcrumb(route, routeState, httpClient) {
|
||||||
|
const [base, queryParams] = route.split('?');
|
||||||
|
if (!base) return null;
|
||||||
|
// This condition is true for any auto generated 20 character long,
|
||||||
|
// URL-safe, base64-encoded document ID by elasticsearch
|
||||||
|
if (RegExp(/^[0-9a-z_-]{20}$/i).test(base)) {
|
||||||
|
const { action } = queryString.parse(`?${queryParams}`);
|
||||||
|
switch (action) {
|
||||||
|
case DESTINATION_ACTIONS.UPDATE_DESTINATION:
|
||||||
|
const destinationName = _.get(routeState, 'destinationToEdit.name', base);
|
||||||
|
const destinationBreadcrumbs = [{ text: destinationName, href: `/destinations/${base}` }];
|
||||||
|
if (action === DESTINATION_ACTIONS.UPDATE_DESTINATION) {
|
||||||
|
destinationBreadcrumbs.push({ text: 'Update destination', href: '/' });
|
||||||
|
}
|
||||||
|
return destinationBreadcrumbs;
|
||||||
|
default:
|
||||||
|
// TODO::Everything else is considered as monitor, we should break this.
|
||||||
|
let monitorName = base;
|
||||||
|
try {
|
||||||
|
const response = await httpClient.get(`../api/alerting/monitors/${base}`);
|
||||||
|
if (response.ok) {
|
||||||
|
monitorName = response.resp.name;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
const breadcrumbs = [{ text: monitorName, href: `/monitors/${base}` }];
|
||||||
|
if (action === MONITOR_ACTIONS.UPDATE_MONITOR)
|
||||||
|
breadcrumbs.push({ text: 'Update monitor', href: '/' });
|
||||||
|
if (action === TRIGGER_ACTIONS.CREATE_TRIGGER)
|
||||||
|
breadcrumbs.push({ text: 'Create trigger', href: '/' });
|
||||||
|
if (action === TRIGGER_ACTIONS.UPDATE_TRIGGER)
|
||||||
|
breadcrumbs.push({ text: 'Update trigger', href: '/' });
|
||||||
|
return breadcrumbs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'#': { text: 'Alerting', href: '/' },
|
||||||
|
monitors: { text: 'Monitors', href: '/monitors' },
|
||||||
|
dashboard: { text: 'Dashboard', href: '/dashboard' },
|
||||||
|
destinations: { text: 'Destinations', href: '/destinations' },
|
||||||
|
'create-monitor': [
|
||||||
|
{ text: 'Monitors', href: '/monitors' },
|
||||||
|
{ text: 'Create monitor', href: APP_PATH.CREATE_MONITOR },
|
||||||
|
],
|
||||||
|
'create-destination': [
|
||||||
|
{ text: 'Destinations', href: '/destinations' },
|
||||||
|
{ text: 'Create destination', href: APP_PATH.CREATE_DESTINATION },
|
||||||
|
],
|
||||||
|
}[base];
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { mount, shallow } from 'enzyme';
|
||||||
|
import { EuiBreadcrumbs } from '@elastic/eui';
|
||||||
|
|
||||||
|
import Breadcrumbs, {
|
||||||
|
createEuiBreadcrumb,
|
||||||
|
getBreadcrumbs,
|
||||||
|
parseLocationHash,
|
||||||
|
getBreadcrumb,
|
||||||
|
} from './Breadcrumbs';
|
||||||
|
import { historyMock, httpClientMock } from '../../../test/mocks';
|
||||||
|
import { MONITOR_ACTIONS, TRIGGER_ACTIONS } from '../../utils/constants';
|
||||||
|
|
||||||
|
const monitorId = 'soDk30SjdsekoaSoMcj1';
|
||||||
|
const location = {
|
||||||
|
hash: '',
|
||||||
|
pathname: '/monitors/random_id_20_chars__',
|
||||||
|
search: '',
|
||||||
|
state: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Breadcrumbs', () => {
|
||||||
|
const title = 'Alerting';
|
||||||
|
httpClientMock.get = jest.fn().mockResolvedValue({ ok: true, resp: { name: 'random monitor' } });
|
||||||
|
delete global.window.location;
|
||||||
|
global.window.location = { hash: '' };
|
||||||
|
|
||||||
|
test('renders', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<Breadcrumbs
|
||||||
|
title={title}
|
||||||
|
location={location}
|
||||||
|
httpClient={httpClientMock}
|
||||||
|
history={historyMock}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calls getBreadcrumbs on mount and when pathname+search are updated', () => {
|
||||||
|
const getBreadcrumbs = jest.spyOn(Breadcrumbs.prototype, 'getBreadcrumbs');
|
||||||
|
const wrapper = mount(
|
||||||
|
<Breadcrumbs
|
||||||
|
title={title}
|
||||||
|
location={location}
|
||||||
|
httpClient={httpClientMock}
|
||||||
|
history={historyMock}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getBreadcrumbs).toHaveBeenCalledTimes(1);
|
||||||
|
wrapper.setProps({ location: { ...location, search: '?search=new' } });
|
||||||
|
expect(getBreadcrumbs).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createEuiBreadcrumbs', () => {
|
||||||
|
test('creates breadcrumbs for EuiBreadcrumbs', () => {
|
||||||
|
const breadcrumb = { text: 'This is a breadcrumb', href: '/this-is-the-href' };
|
||||||
|
expect(createEuiBreadcrumb(breadcrumb, historyMock)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseLocationHash', () => {
|
||||||
|
test('correctly parses location hash', () => {
|
||||||
|
const hash = `#/monitors/${monitorId}?action=${TRIGGER_ACTIONS.CREATE_TRIGGER}`;
|
||||||
|
expect(parseLocationHash(hash)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('filters out falsy string values', () => {
|
||||||
|
const hash = '#/monitors/';
|
||||||
|
expect(parseLocationHash(hash)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getBreadcrumb', () => {
|
||||||
|
const routeState = { destinationToEdit: { name: 'unique_name' } };
|
||||||
|
test('returns null if falsy base value', async () => {
|
||||||
|
expect(await getBreadcrumb('', {}, httpClientMock)).toBe(null);
|
||||||
|
expect(
|
||||||
|
await getBreadcrumb(`?action=${TRIGGER_ACTIONS.CREATE_TRIGGER}`, {}, httpClientMock)
|
||||||
|
).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns correct constant breadcrumbs', async () => {
|
||||||
|
expect(await getBreadcrumb('#', {}, httpClientMock)).toMatchSnapshot();
|
||||||
|
expect(await getBreadcrumb('monitors', {}, httpClientMock)).toMatchSnapshot();
|
||||||
|
expect(await getBreadcrumb('dashboard', {}, httpClientMock)).toMatchSnapshot();
|
||||||
|
expect(await getBreadcrumb('destinations', {}, httpClientMock)).toMatchSnapshot();
|
||||||
|
expect(await getBreadcrumb('create-monitor', {}, httpClientMock)).toMatchSnapshot();
|
||||||
|
expect(await getBreadcrumb('create-destination', {}, httpClientMock)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when matching document IDs', () => {
|
||||||
|
test('calls get monitor route', async () => {
|
||||||
|
httpClientMock.get.mockResolvedValue({ ok: true, resp: { name: 'random_name' } });
|
||||||
|
await getBreadcrumb(monitorId, {}, httpClientMock);
|
||||||
|
expect(httpClientMock.get).toHaveBeenCalled();
|
||||||
|
expect(httpClientMock.get).toHaveBeenCalledWith(`../api/alerting/monitors/${monitorId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns monitor name', async () => {
|
||||||
|
httpClientMock.get.mockResolvedValue({ ok: true, resp: { name: 'random_name' } });
|
||||||
|
expect(await getBreadcrumb(monitorId, {}, httpClientMock)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses monitor id as name if request fails', async () => {
|
||||||
|
httpClientMock.get.mockRejectedValue({ ok: true, resp: { name: 'random_name' } });
|
||||||
|
expect(await getBreadcrumb(monitorId, {}, httpClientMock)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses monitor id as name if ok=false', async () => {
|
||||||
|
httpClientMock.get.mockResolvedValue({ ok: false, resp: { name: 'random_name' } });
|
||||||
|
expect(await getBreadcrumb(monitorId, {}, httpClientMock)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('adds appropriate action breadcrumb', async () => {
|
||||||
|
httpClientMock.get.mockResolvedValue({ ok: true, resp: { name: 'random_name' } });
|
||||||
|
expect(
|
||||||
|
await getBreadcrumb(
|
||||||
|
`${monitorId}?action=${MONITOR_ACTIONS.UPDATE_MONITOR}`,
|
||||||
|
{},
|
||||||
|
httpClientMock
|
||||||
|
)
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
await getBreadcrumb(
|
||||||
|
`${monitorId}?action=${TRIGGER_ACTIONS.CREATE_TRIGGER}`,
|
||||||
|
{},
|
||||||
|
httpClientMock
|
||||||
|
)
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
await getBreadcrumb(
|
||||||
|
`${monitorId}?action=${TRIGGER_ACTIONS.UPDATE_TRIGGER}`,
|
||||||
|
{},
|
||||||
|
httpClientMock
|
||||||
|
)
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,157 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Breadcrumbs renders 1`] = `
|
||||||
|
<EuiBreadcrumbs
|
||||||
|
breadcrumbs={Array []}
|
||||||
|
responsive={false}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": "0px 15px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
truncate={true}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`createEuiBreadcrumbs creates breadcrumbs for EuiBreadcrumbs 1`] = `
|
||||||
|
Object {
|
||||||
|
"href": "#/this-is-the-href",
|
||||||
|
"onClick": [Function],
|
||||||
|
"text": "This is a breadcrumb",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb returns correct constant breadcrumbs 1`] = `
|
||||||
|
Object {
|
||||||
|
"href": "/",
|
||||||
|
"text": "Alerting",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb returns correct constant breadcrumbs 2`] = `
|
||||||
|
Object {
|
||||||
|
"href": "/monitors",
|
||||||
|
"text": "Monitors",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb returns correct constant breadcrumbs 3`] = `
|
||||||
|
Object {
|
||||||
|
"href": "/dashboard",
|
||||||
|
"text": "Dashboard",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb returns correct constant breadcrumbs 4`] = `
|
||||||
|
Object {
|
||||||
|
"href": "/destinations",
|
||||||
|
"text": "Destinations",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb returns correct constant breadcrumbs 5`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"href": "/monitors",
|
||||||
|
"text": "Monitors",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"href": "/create-monitor",
|
||||||
|
"text": "Create monitor",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb returns correct constant breadcrumbs 6`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"href": "/destinations",
|
||||||
|
"text": "Destinations",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"href": "/create-destination",
|
||||||
|
"text": "Create destination",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb when matching document IDs adds appropriate action breadcrumb 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"href": "/monitors/soDk30SjdsekoaSoMcj1",
|
||||||
|
"text": "random_name",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"href": "/",
|
||||||
|
"text": "Update monitor",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb when matching document IDs adds appropriate action breadcrumb 2`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"href": "/monitors/soDk30SjdsekoaSoMcj1",
|
||||||
|
"text": "random_name",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"href": "/",
|
||||||
|
"text": "Create trigger",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb when matching document IDs adds appropriate action breadcrumb 3`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"href": "/monitors/soDk30SjdsekoaSoMcj1",
|
||||||
|
"text": "random_name",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"href": "/",
|
||||||
|
"text": "Update trigger",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb when matching document IDs returns monitor name 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"href": "/monitors/soDk30SjdsekoaSoMcj1",
|
||||||
|
"text": "random_name",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb when matching document IDs uses monitor id as name if ok=false 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"href": "/monitors/soDk30SjdsekoaSoMcj1",
|
||||||
|
"text": "soDk30SjdsekoaSoMcj1",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`getBreadcrumb when matching document IDs uses monitor id as name if request fails 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"href": "/monitors/soDk30SjdsekoaSoMcj1",
|
||||||
|
"text": "soDk30SjdsekoaSoMcj1",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`parseLocationHash correctly parses location hash 1`] = `
|
||||||
|
Array [
|
||||||
|
"#",
|
||||||
|
"monitors",
|
||||||
|
"soDk30SjdsekoaSoMcj1?action=create-trigger",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`parseLocationHash filters out falsy string values 1`] = `
|
||||||
|
Array [
|
||||||
|
"#",
|
||||||
|
"monitors",
|
||||||
|
]
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Breadcrumbs from './Breadcrumbs';
|
||||||
|
|
||||||
|
export default Breadcrumbs;
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const ChartContainer = ({ children, style }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderRadius: '5px',
|
||||||
|
padding: '10px',
|
||||||
|
border: '1px solid #D9D9D9',
|
||||||
|
height: '250px',
|
||||||
|
width: '100%',
|
||||||
|
...style,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
ChartContainer.propTypes = {
|
||||||
|
style: PropTypes.object,
|
||||||
|
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]).isRequired,
|
||||||
|
};
|
||||||
|
ChartContainer.defaultPropTypes = {
|
||||||
|
style: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ChartContainer };
|
|
@ -0,0 +1,470 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This file has been cloned and modified from the original source(https://github.com/uber/react-vis/blob/35c8950722e9ad60214399291eda731ac31af267/src/plot/highlight.js) due to its limitations.
|
||||||
|
We should revert this to an Actual Highlight Component from react-vis when it is ready for use with
|
||||||
|
Controlled Highlight, allow only Drag and not to rebrush.
|
||||||
|
* Removes capability for brushing on Area
|
||||||
|
* Adds capability for dragging on chart
|
||||||
|
* Styles Component as per the our mocks
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { ScaleUtils, AbstractSeries } from 'react-vis';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const getLocs = evt => {
|
||||||
|
const targetBounding = evt.currentTarget.getBoundingClientRect();
|
||||||
|
const offsetX = evt.clientX - targetBounding.left;
|
||||||
|
const offsetY = evt.clientY - targetBounding.top;
|
||||||
|
return {
|
||||||
|
xLoc: offsetX,
|
||||||
|
yLoc: offsetY,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Highlight extends AbstractSeries {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
dragging: false,
|
||||||
|
brushArea: { bottom: 0, right: 0, left: 0, top: 0 },
|
||||||
|
brushing: false,
|
||||||
|
startLocX: 0,
|
||||||
|
startLocY: 0,
|
||||||
|
dragArea: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { highlightRef } = this.props;
|
||||||
|
if (highlightRef) {
|
||||||
|
highlightRef({
|
||||||
|
setHighlightedArea: this.setHighlightedArea,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (!isEqual(this.props, prevProps)) {
|
||||||
|
this.setHighlightedArea({
|
||||||
|
...this.props.highlightedArea,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDrawArea(xLoc, yLoc) {
|
||||||
|
const { startLocX, startLocY } = this.state;
|
||||||
|
const {
|
||||||
|
enableX,
|
||||||
|
enableY,
|
||||||
|
highlightWidth,
|
||||||
|
highlightHeight,
|
||||||
|
innerWidth,
|
||||||
|
innerHeight,
|
||||||
|
marginLeft,
|
||||||
|
marginRight,
|
||||||
|
marginBottom,
|
||||||
|
marginTop,
|
||||||
|
} = this.props;
|
||||||
|
const plotHeight = innerHeight + marginTop + marginBottom;
|
||||||
|
const plotWidth = innerWidth + marginLeft + marginRight;
|
||||||
|
const touchWidth = highlightWidth || plotWidth;
|
||||||
|
const touchHeight = highlightHeight || plotHeight;
|
||||||
|
|
||||||
|
return {
|
||||||
|
bottom: enableY ? Math.max(startLocY, yLoc) : touchHeight,
|
||||||
|
right: enableX ? Math.max(startLocX, xLoc) : touchWidth,
|
||||||
|
left: enableX ? Math.min(xLoc, startLocX) : 0,
|
||||||
|
top: enableY ? Math.min(yLoc, startLocY) : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDragArea(xLoc, yLoc) {
|
||||||
|
const { enableX, enableY } = this.props;
|
||||||
|
const { startLocX, startLocY, dragArea } = this.state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
bottom: dragArea.bottom + (enableY ? yLoc - startLocY : 0),
|
||||||
|
left: dragArea.left + (enableX ? xLoc - startLocX : 0),
|
||||||
|
right: dragArea.right + (enableX ? xLoc - startLocX : 0),
|
||||||
|
top: dragArea.top + (enableY ? yLoc - startLocY : 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_clickedOutsideDrag(xLoc, yLoc) {
|
||||||
|
const { enableX, enableY, marginLeft, marginTop } = this.props;
|
||||||
|
const {
|
||||||
|
dragArea,
|
||||||
|
brushArea: { left, right, top, bottom },
|
||||||
|
} = this.state;
|
||||||
|
const actualXLoc = xLoc + marginLeft;
|
||||||
|
const actualYLoc = yLoc + marginTop;
|
||||||
|
const clickedOutsideDragX = dragArea && (actualXLoc < left || actualXLoc > right);
|
||||||
|
const clickedOutsideDragY = dragArea && (actualYLoc < top || actualYLoc > bottom);
|
||||||
|
if (enableX && enableY) {
|
||||||
|
return clickedOutsideDragX || clickedOutsideDragY;
|
||||||
|
}
|
||||||
|
if (enableX) {
|
||||||
|
return clickedOutsideDragX;
|
||||||
|
}
|
||||||
|
if (enableY) {
|
||||||
|
return clickedOutsideDragY;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_convertAreaToCoordinates(brushArea) {
|
||||||
|
// NOTE only continuous scales are supported for brushing/getting coordinates back
|
||||||
|
const { enableX, enableY, marginLeft, marginTop } = this.props;
|
||||||
|
const xScale = ScaleUtils.getAttributeScale(this.props, 'x');
|
||||||
|
const yScale = ScaleUtils.getAttributeScale(this.props, 'y');
|
||||||
|
|
||||||
|
// Ensure that users wishes are being respected about which scales are evaluated
|
||||||
|
// this is specifically enabled to ensure brushing on mixed categorical and linear
|
||||||
|
// charts will run as expected
|
||||||
|
|
||||||
|
if (enableX && enableY) {
|
||||||
|
return {
|
||||||
|
bottom: yScale.invert(brushArea.bottom),
|
||||||
|
left: xScale.invert(brushArea.left - marginLeft),
|
||||||
|
right: xScale.invert(brushArea.right - marginLeft),
|
||||||
|
top: yScale.invert(brushArea.top),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableY) {
|
||||||
|
return {
|
||||||
|
bottom: yScale.invert(brushArea.bottom - marginTop),
|
||||||
|
top: yScale.invert(brushArea.top - marginTop),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableX) {
|
||||||
|
return {
|
||||||
|
left: xScale.invert(brushArea.left - marginLeft),
|
||||||
|
right: xScale.invert(brushArea.right - marginLeft),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is a public method which can be accessed via ref which will allow
|
||||||
|
consumer to control Highlight Window.
|
||||||
|
*/
|
||||||
|
setHighlightedArea = highlightArea => {
|
||||||
|
const {
|
||||||
|
highlightHeight,
|
||||||
|
highlightWidth,
|
||||||
|
innerWidth,
|
||||||
|
innerHeight,
|
||||||
|
marginLeft,
|
||||||
|
marginBottom,
|
||||||
|
marginTop,
|
||||||
|
marginRight,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const xScale = ScaleUtils.getAttributeScale(this.props, 'x');
|
||||||
|
const yScale = ScaleUtils.getAttributeScale(this.props, 'y');
|
||||||
|
|
||||||
|
const plotHeight = innerHeight + marginTop + marginBottom;
|
||||||
|
const plotWidth = innerWidth + marginLeft + marginRight;
|
||||||
|
|
||||||
|
let bottomPos = highlightHeight || plotHeight;
|
||||||
|
if (highlightArea.bottom) {
|
||||||
|
bottomPos = yScale(highlightArea.bottom) + marginTop;
|
||||||
|
}
|
||||||
|
let topPos = 0;
|
||||||
|
if (highlightArea.top) {
|
||||||
|
topPos = yScale(highlightArea.top) + marginTop;
|
||||||
|
}
|
||||||
|
let leftPos = 0 + marginLeft;
|
||||||
|
if (highlightArea.left) {
|
||||||
|
leftPos = xScale(highlightArea.left) + marginLeft;
|
||||||
|
}
|
||||||
|
let rightPos = highlightWidth || plotWidth;
|
||||||
|
if (highlightArea.right) {
|
||||||
|
rightPos = xScale(highlightArea.right) + marginLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
const brushArea = {
|
||||||
|
bottom: bottomPos,
|
||||||
|
right: rightPos,
|
||||||
|
left: leftPos,
|
||||||
|
top: topPos,
|
||||||
|
};
|
||||||
|
this.setState({ brushArea, dragArea: brushArea });
|
||||||
|
};
|
||||||
|
|
||||||
|
startBrushing(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const { onDragStart, drag } = this.props;
|
||||||
|
const { dragArea } = this.state;
|
||||||
|
const { xLoc, yLoc } = getLocs(e);
|
||||||
|
const clickedOutsideDrag = this._clickedOutsideDrag(xLoc, yLoc);
|
||||||
|
if (!clickedOutsideDrag && drag) {
|
||||||
|
this.setState({
|
||||||
|
dragging: true,
|
||||||
|
brushArea: dragArea,
|
||||||
|
brushing: false,
|
||||||
|
startLocX: xLoc,
|
||||||
|
startLocY: yLoc,
|
||||||
|
});
|
||||||
|
if (onDragStart) {
|
||||||
|
onDragStart(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopBrushing(e) {
|
||||||
|
const { brushing, dragging, brushArea } = this.state;
|
||||||
|
// Quickly short-circuit if the user isn't brushing in our component
|
||||||
|
if (!brushing && !dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { onBrushEnd, onDragEnd, drag } = this.props;
|
||||||
|
const noHorizontal = Math.abs(brushArea.right - brushArea.left) < 5;
|
||||||
|
const noVertical = Math.abs(brushArea.top - brushArea.bottom) < 5;
|
||||||
|
// Invoke the callback with null if the selected area was < 5px
|
||||||
|
const isNulled = noVertical || noHorizontal;
|
||||||
|
// Clear the draw area
|
||||||
|
this.setState({
|
||||||
|
brushing: false,
|
||||||
|
dragging: false,
|
||||||
|
brushArea: drag ? brushArea : { top: 0, right: 0, bottom: 0, left: 0 },
|
||||||
|
startLocX: 0,
|
||||||
|
startLocY: 0,
|
||||||
|
dragArea: drag && !isNulled && brushArea,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (brushing && onBrushEnd) {
|
||||||
|
onBrushEnd(!isNulled ? this._convertAreaToCoordinates(brushArea) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drag && onDragEnd) {
|
||||||
|
onDragEnd(!isNulled ? this._convertAreaToCoordinates(brushArea) : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBrush(e) {
|
||||||
|
const { marginLeft, marginRight, innerWidth, onBrush, onDrag, drag } = this.props;
|
||||||
|
const { brushing, dragging } = this.state;
|
||||||
|
const { xLoc, yLoc } = getLocs(e);
|
||||||
|
if (brushing) {
|
||||||
|
const brushArea = this._getDrawArea(xLoc, yLoc);
|
||||||
|
this.setState({ brushArea });
|
||||||
|
if (onBrush) {
|
||||||
|
onBrush(this._convertAreaToCoordinates(brushArea));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drag && dragging) {
|
||||||
|
const brushArea = this._getDragArea(xLoc, yLoc);
|
||||||
|
const rightBoundary = innerWidth + marginRight;
|
||||||
|
const leftBoundary = marginLeft;
|
||||||
|
if (brushArea.right <= rightBoundary && brushArea.left >= leftBoundary) {
|
||||||
|
this.setState({ brushArea });
|
||||||
|
if (onDrag) {
|
||||||
|
onDrag(this._convertAreaToCoordinates(brushArea));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getHighlighterStyles() {
|
||||||
|
const { isDarkMode } = this.props;
|
||||||
|
if (isDarkMode) {
|
||||||
|
return {
|
||||||
|
fill: 'black',
|
||||||
|
opacity: 0.1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fill: 'white',
|
||||||
|
opacity: 0.8,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
color,
|
||||||
|
className,
|
||||||
|
highlightHeight,
|
||||||
|
highlightWidth,
|
||||||
|
highlightX,
|
||||||
|
highlightY,
|
||||||
|
innerWidth,
|
||||||
|
innerHeight,
|
||||||
|
marginLeft,
|
||||||
|
marginRight,
|
||||||
|
marginTop,
|
||||||
|
marginBottom,
|
||||||
|
opacity,
|
||||||
|
} = this.props;
|
||||||
|
const {
|
||||||
|
brushArea: { left, right, top, bottom },
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
let leftPos = 0;
|
||||||
|
|
||||||
|
if (highlightX) {
|
||||||
|
const xScale = ScaleUtils.getAttributeScale(this.props, 'x');
|
||||||
|
leftPos = xScale(highlightX);
|
||||||
|
}
|
||||||
|
|
||||||
|
let topPos = 0;
|
||||||
|
if (highlightY) {
|
||||||
|
const yScale = ScaleUtils.getAttributeScale(this.props, 'y');
|
||||||
|
topPos = yScale(highlightY);
|
||||||
|
}
|
||||||
|
|
||||||
|
const plotWidth = marginLeft + marginRight + innerWidth;
|
||||||
|
const plotHeight = marginTop + marginBottom + innerHeight;
|
||||||
|
const touchWidth = highlightWidth || plotWidth;
|
||||||
|
const touchHeight = highlightHeight || plotHeight;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g
|
||||||
|
transform={`translate(${leftPos}, ${topPos})`}
|
||||||
|
className={`${className} rv-highlight-container`}
|
||||||
|
>
|
||||||
|
{/* An overlay on a graph which allows dragging */}
|
||||||
|
<rect
|
||||||
|
className="rv-mouse-target"
|
||||||
|
opacity="0"
|
||||||
|
cursor="move"
|
||||||
|
x={marginLeft}
|
||||||
|
y={0}
|
||||||
|
width={Math.max(touchWidth, 0)}
|
||||||
|
height={Math.max(touchHeight, 0)}
|
||||||
|
onMouseDown={e => this.startBrushing(e)}
|
||||||
|
onMouseMove={e => this.onBrush(e)}
|
||||||
|
onMouseUp={e => this.stopBrushing(e)}
|
||||||
|
onMouseLeave={e => this.stopBrushing(e)}
|
||||||
|
// preventDefault() so that mouse event emulation does not happen
|
||||||
|
onTouchEnd={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.stopBrushing(e);
|
||||||
|
}}
|
||||||
|
onTouchCancel={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.stopBrushing(e);
|
||||||
|
}}
|
||||||
|
onContextMenu={e => e.preventDefault()}
|
||||||
|
onContextMenuCapture={e => e.preventDefault()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Left side of overlay which allows us to give an opacity to cover */}
|
||||||
|
<rect
|
||||||
|
pointerEvents="none"
|
||||||
|
className="rv-mouse-target"
|
||||||
|
x={marginLeft}
|
||||||
|
y="0"
|
||||||
|
width={Math.max(left - marginLeft, 0)}
|
||||||
|
height={Math.max(touchHeight, 0)}
|
||||||
|
onMouseLeave={e => e.preventDefault()}
|
||||||
|
{...this.getHighlighterStyles()}
|
||||||
|
/>
|
||||||
|
{/* A Center Highlighter */}
|
||||||
|
<rect
|
||||||
|
pointerEvents="none"
|
||||||
|
opacity={opacity}
|
||||||
|
fill={color}
|
||||||
|
x={left}
|
||||||
|
y={top}
|
||||||
|
width={Math.max(0, right - left)}
|
||||||
|
height={Math.max(0, bottom - top)}
|
||||||
|
onMouseLeave={e => e.preventDefault()}
|
||||||
|
/>
|
||||||
|
{/* Right side of overlay which allows us to give an opacity to cover */}
|
||||||
|
<rect
|
||||||
|
pointerEvents="none"
|
||||||
|
className="rv-mouse-target"
|
||||||
|
x={right}
|
||||||
|
y="0"
|
||||||
|
width={Math.max(touchWidth - right, 0)}
|
||||||
|
height={Math.max(touchHeight, 0)}
|
||||||
|
onMouseLeave={e => e.preventDefault()}
|
||||||
|
{...this.getHighlighterStyles()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Draws border lines on Highlighted area */}
|
||||||
|
<g>
|
||||||
|
<rect
|
||||||
|
pointerEvents="none"
|
||||||
|
x={left}
|
||||||
|
y={top}
|
||||||
|
width="1"
|
||||||
|
height={Math.max(0, bottom - top)}
|
||||||
|
fill="rgb(151,151,151)"
|
||||||
|
stroke="none"
|
||||||
|
onMouseLeave={e => e.preventDefault()}
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
pointerEvents="none"
|
||||||
|
x={right}
|
||||||
|
y={top}
|
||||||
|
width="1"
|
||||||
|
height={Math.max(0, bottom - top)}
|
||||||
|
fill="rgb(151,151,151)"
|
||||||
|
stroke="none"
|
||||||
|
onMouseLeave={e => e.preventDefault()}
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
pointerEvents="none"
|
||||||
|
x1={left}
|
||||||
|
x2={right}
|
||||||
|
stroke="rgb(151,151,151)"
|
||||||
|
strokeWidth={2}
|
||||||
|
onMouseLeave={e => e.preventDefault()}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Highlight.displayName = 'HighlightOverlay';
|
||||||
|
|
||||||
|
Highlight.defaultProps = {
|
||||||
|
color: 'rgb(77, 182, 172)',
|
||||||
|
className: '',
|
||||||
|
enableX: true,
|
||||||
|
enableY: true,
|
||||||
|
highlightRef: null,
|
||||||
|
opacity: 0.3,
|
||||||
|
};
|
||||||
|
|
||||||
|
Highlight.propTypes = {
|
||||||
|
...AbstractSeries.propTypes,
|
||||||
|
enableX: PropTypes.bool,
|
||||||
|
enableY: PropTypes.bool,
|
||||||
|
highlightHeight: PropTypes.number,
|
||||||
|
highlightWidth: PropTypes.number,
|
||||||
|
highlightX: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
highlightY: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
highlightRef: PropTypes.func,
|
||||||
|
onBrushStart: PropTypes.func,
|
||||||
|
onDragStart: PropTypes.func,
|
||||||
|
onBrush: PropTypes.func,
|
||||||
|
onDrag: PropTypes.func,
|
||||||
|
onBrushEnd: PropTypes.func,
|
||||||
|
onDragEnd: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Highlight;
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, EuiTitle } from '@elastic/eui';
|
||||||
|
|
||||||
|
const ContentPanel = ({
|
||||||
|
title = '',
|
||||||
|
titleSize = 'l',
|
||||||
|
bodyStyles = {},
|
||||||
|
panelStyles = {},
|
||||||
|
horizontalRuleClassName = '',
|
||||||
|
actions,
|
||||||
|
children,
|
||||||
|
}) => (
|
||||||
|
<EuiPanel style={{ paddingLeft: '0px', paddingRight: '0px', ...panelStyles }}>
|
||||||
|
<EuiFlexGroup style={{ padding: '0px 10px' }} justifyContent="spaceBetween" alignItems="center">
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiTitle size={titleSize}>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||||
|
{Array.isArray(actions) ? (
|
||||||
|
actions.map((action, idx) => <EuiFlexItem key={idx}>{action}</EuiFlexItem>)
|
||||||
|
) : (
|
||||||
|
<EuiFlexItem>{actions}</EuiFlexItem>
|
||||||
|
)}
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
|
||||||
|
<EuiHorizontalRule margin="xs" className={horizontalRuleClassName} />
|
||||||
|
|
||||||
|
<div style={{ padding: '0px 10px', ...bodyStyles }}>{children}</div>
|
||||||
|
</EuiPanel>
|
||||||
|
);
|
||||||
|
|
||||||
|
ContentPanel.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
titleSize: PropTypes.string,
|
||||||
|
bodyStyles: PropTypes.object,
|
||||||
|
panelStyles: PropTypes.object,
|
||||||
|
horizontalRuleClassName: PropTypes.string,
|
||||||
|
actions: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]),
|
||||||
|
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf([PropTypes.node])]).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContentPanel;
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
|
||||||
|
import ContentPanel from './ContentPanel';
|
||||||
|
|
||||||
|
describe('ContentPanel', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<ContentPanel title="Test Content Panel">
|
||||||
|
<div>Test</div>
|
||||||
|
</ContentPanel>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`ContentPanel renders 1`] = `
|
||||||
|
<div
|
||||||
|
class="euiPanel euiPanel--paddingMedium"
|
||||||
|
style="padding-left:0px;padding-right:0px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||||
|
style="padding:0px 10px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFlexItem"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="euiTitle euiTitle--large"
|
||||||
|
>
|
||||||
|
Test Content Panel
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFlexItem"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr
|
||||||
|
class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginXSmall"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style="padding:0px 10px"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
Test
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ContentPanel from './ContentPanel';
|
||||||
|
|
||||||
|
export default ContentPanel;
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class DelayedLoader extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
displayLoader: false,
|
||||||
|
};
|
||||||
|
if (typeof props.children !== 'function') {
|
||||||
|
throw new Error('Children should be function');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.isLoading) {
|
||||||
|
this.setTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const { isLoading } = this.props;
|
||||||
|
// Setting up the loader to be visible only when network is too slow
|
||||||
|
if (isLoading !== prevProps.isLoading) {
|
||||||
|
if (isLoading) {
|
||||||
|
this.setTimer();
|
||||||
|
} else {
|
||||||
|
this.clearTimer();
|
||||||
|
this.setState({ displayLoader: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.clearTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimer = () => {
|
||||||
|
if (this.timer) {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimer = () => {
|
||||||
|
this.timer = setTimeout(this.handleDisplayLoader, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDisplayLoader = () => this.setState({ displayLoader: true });
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { displayLoader } = this.state;
|
||||||
|
return this.props.children(displayLoader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelayedLoader.propTypes = {
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
children: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DelayedLoader;
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render, mount } from 'enzyme';
|
||||||
|
import DelayedLoader from '../DelayedLoader';
|
||||||
|
|
||||||
|
describe('<DelayedLoader/>', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders', () => {
|
||||||
|
expect(
|
||||||
|
render(
|
||||||
|
<DelayedLoader isLoading={false}>
|
||||||
|
{showLoader => <div style={{ opacity: showLoader ? '0.2' : '1' }} />}
|
||||||
|
</DelayedLoader>
|
||||||
|
)
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should set Timer for 1 seconds if initial loading is true', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<DelayedLoader isLoading={true}>
|
||||||
|
{showLoader => <div style={{ opacity: showLoader ? '0.2' : '1' }} />}
|
||||||
|
</DelayedLoader>
|
||||||
|
);
|
||||||
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||||
|
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test('should clear Timer on componentWillUnmount if exists', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<DelayedLoader isLoading={true}>
|
||||||
|
{showLoader => <div style={{ opacity: showLoader ? '0.2' : '1' }} />}
|
||||||
|
</DelayedLoader>
|
||||||
|
);
|
||||||
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
||||||
|
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
|
||||||
|
wrapper.unmount();
|
||||||
|
expect(clearTimeout).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not show loader if data fetching is finished before threshold', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<DelayedLoader isLoading={true}>
|
||||||
|
{showLoader => <div style={{ opacity: showLoader ? '0.2' : '1' }} />}
|
||||||
|
</DelayedLoader>
|
||||||
|
);
|
||||||
|
wrapper.setProps({ isLoading: false });
|
||||||
|
expect(clearTimeout).toHaveBeenCalledTimes(1);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show loader if data fetching takes more than threshold', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<DelayedLoader isLoading={false}>
|
||||||
|
{showLoader => <div style={{ opacity: showLoader ? '0.2' : '1' }} />}
|
||||||
|
</DelayedLoader>
|
||||||
|
);
|
||||||
|
wrapper.setProps({ isLoading: true });
|
||||||
|
jest.runAllTimers();
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw an error if children is not function', () => {
|
||||||
|
expect(() => {
|
||||||
|
render(
|
||||||
|
<DelayedLoader isLoading={false}>
|
||||||
|
<div />
|
||||||
|
</DelayedLoader>
|
||||||
|
);
|
||||||
|
}).toThrow('Children should be function');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<DelayedLoader/> renders 1`] = `
|
||||||
|
<div
|
||||||
|
style="opacity:1"
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<DelayedLoader/> should not show loader if data fetching is finished before threshold 1`] = `
|
||||||
|
<DelayedLoader
|
||||||
|
isLoading={false}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"opacity": "1",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</DelayedLoader>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<DelayedLoader/> should set Timer for 1 seconds if initial loading is true 1`] = `
|
||||||
|
<DelayedLoader
|
||||||
|
isLoading={true}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"opacity": "1",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</DelayedLoader>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<DelayedLoader/> should show loader if data fetching takes more than threshold 1`] = `
|
||||||
|
<DelayedLoader
|
||||||
|
isLoading={true}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"opacity": "0.2",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</DelayedLoader>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import DelayedLoader from './DelayedLoader';
|
||||||
|
|
||||||
|
export default DelayedLoader;
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiFlyoutFooter } from '@elastic/eui';
|
||||||
|
|
||||||
|
import Flyouts from './flyouts';
|
||||||
|
|
||||||
|
const getFlyoutData = ({ type, payload }) => {
|
||||||
|
const flyout = Flyouts[type];
|
||||||
|
if (!flyout || typeof flyout !== 'function') return null;
|
||||||
|
return flyout(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
flyout: PropTypes.object,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Flyout = ({ flyout, onClose }) => {
|
||||||
|
if (!flyout) return null;
|
||||||
|
const flyoutData = getFlyoutData(flyout);
|
||||||
|
if (!flyoutData) return null;
|
||||||
|
const {
|
||||||
|
flyoutProps = {},
|
||||||
|
headerProps = {},
|
||||||
|
bodyProps = {},
|
||||||
|
footerProps = {},
|
||||||
|
header = null,
|
||||||
|
body = null,
|
||||||
|
footer = null,
|
||||||
|
} = flyoutData;
|
||||||
|
|
||||||
|
const flyoutHeader = header && <EuiFlyoutHeader {...headerProps}>{header}</EuiFlyoutHeader>;
|
||||||
|
const flyoutBody = body && <EuiFlyoutBody {...bodyProps}>{body}</EuiFlyoutBody>;
|
||||||
|
const flyoutFooter = footer && <EuiFlyoutFooter {...footerProps}>{footer}</EuiFlyoutFooter>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiFlyout onClose={onClose} {...flyoutProps}>
|
||||||
|
{flyoutHeader}
|
||||||
|
{flyoutBody}
|
||||||
|
{flyoutFooter}
|
||||||
|
</EuiFlyout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Flyout.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default Flyout;
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import Flyout from './Flyout';
|
||||||
|
import Flyouts from './flyouts';
|
||||||
|
jest.unmock('./flyouts');
|
||||||
|
|
||||||
|
describe('Flyout', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<Flyout flyout={{ type: 'message', payload: null }} onClose={jest.fn()} />
|
||||||
|
);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders null if no flyout', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<Flyout flyout={{ type: 'definitely no flyout', payload: null }} onClose={jest.fn()} />
|
||||||
|
);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('defaults if bad flyout data', () => {
|
||||||
|
Flyouts.message = jest.fn(() => ({}));
|
||||||
|
const wrapper = shallow(
|
||||||
|
<Flyout flyout={{ type: 'message', payload: null }} onClose={jest.fn()} />
|
||||||
|
);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Flyout defaults if bad flyout data 1`] = `
|
||||||
|
<EuiFlyout
|
||||||
|
onClose={[MockFunction]}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Flyout renders 1`] = `
|
||||||
|
<EuiFlyout
|
||||||
|
aria-labelledby="messageFlyout"
|
||||||
|
maxWidth={500}
|
||||||
|
onClose={[MockFunction]}
|
||||||
|
size="m"
|
||||||
|
>
|
||||||
|
<EuiFlyoutHeader
|
||||||
|
hasBorder={true}
|
||||||
|
>
|
||||||
|
<EuiTitle
|
||||||
|
size="m"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": "25px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
<strong>
|
||||||
|
Message
|
||||||
|
</strong>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiFlyoutHeader>
|
||||||
|
<EuiFlyoutBody>
|
||||||
|
<EuiText
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": "14px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
You have access to a "ctx" variable in your painless scripts and action mustache templates.
|
||||||
|
</p>
|
||||||
|
<h3>
|
||||||
|
Learn More
|
||||||
|
</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<EuiLink
|
||||||
|
href="https://mustache.github.io/mustache.5.html"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
HTML Templates with Mustache.js
|
||||||
|
</EuiLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</EuiText>
|
||||||
|
</EuiFlyoutBody>
|
||||||
|
</EuiFlyout>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Flyout renders null if no flyout 1`] = `""`;
|
|
@ -0,0 +1,182 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Flyouts.message generates message JSON 1`] = `
|
||||||
|
Object {
|
||||||
|
"body": <EuiText
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": "14px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
You have access to a "ctx" variable in your painless scripts and action mustache templates.
|
||||||
|
</p>
|
||||||
|
<h3>
|
||||||
|
Learn More
|
||||||
|
</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<EuiLink
|
||||||
|
href="https://mustache.github.io/mustache.5.html"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
HTML Templates with Mustache.js
|
||||||
|
</EuiLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</EuiText>,
|
||||||
|
"flyoutProps": Object {
|
||||||
|
"aria-labelledby": "messageFlyout",
|
||||||
|
"maxWidth": 500,
|
||||||
|
"size": "m",
|
||||||
|
},
|
||||||
|
"header": <EuiTitle
|
||||||
|
size="m"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": "25px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
<strong>
|
||||||
|
Message
|
||||||
|
</strong>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>,
|
||||||
|
"headerProps": Object {
|
||||||
|
"hasBorder": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Flyouts.messageFrequency generates message JSON 1`] = `
|
||||||
|
Object {
|
||||||
|
"body": <EuiText
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": "14px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Specify message frequency to limit the number of notifications you receive within a given span of time. This setting is especially useful for low severity trigger conditions.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Consider the following example:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
A trigger condition is met.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The monitor sends a message
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Message frequency is set to 10 minutes.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
For the next 10 minutes, even if a trigger condition is met dozens of times, the monitor sends no additional messages. If the trigger condition is met 11 minutes later, the monitor sends another message.
|
||||||
|
</p>
|
||||||
|
</EuiText>,
|
||||||
|
"flyoutProps": Object {
|
||||||
|
"aria-labelledby": "messageFrequencyFlyout",
|
||||||
|
"maxWidth": 500,
|
||||||
|
"size": "m",
|
||||||
|
},
|
||||||
|
"header": <EuiTitle
|
||||||
|
size="m"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": "25px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
<strong>
|
||||||
|
Message frequency
|
||||||
|
</strong>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>,
|
||||||
|
"headerProps": Object {
|
||||||
|
"hasBorder": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Flyouts.triggerCondition generates message JSON 1`] = `
|
||||||
|
Object {
|
||||||
|
"body": <div>
|
||||||
|
<EuiText
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": "14px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
You have access to a "ctx" variable in your painless scripts
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Below shows a quick JSON example of what's available under the "ctx" variable along with the actual results (where possible) for you to reference.
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
|
<EuiSpacer
|
||||||
|
size="m"
|
||||||
|
/>
|
||||||
|
<EuiCodeBlock
|
||||||
|
language="json"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
"monitor": "...",
|
||||||
|
"trigger": "...",
|
||||||
|
"results": "...",
|
||||||
|
"periodStart": "...",
|
||||||
|
"periodEnd": "...",
|
||||||
|
"alert": "...",
|
||||||
|
"error": "..."
|
||||||
|
}
|
||||||
|
</EuiCodeBlock>
|
||||||
|
<EuiSpacer
|
||||||
|
size="m"
|
||||||
|
/>
|
||||||
|
<EuiCodeEditor
|
||||||
|
height="700px"
|
||||||
|
mode="json"
|
||||||
|
readOnly={true}
|
||||||
|
setOptions={
|
||||||
|
Object {
|
||||||
|
"fontSize": "12px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
theme="github"
|
||||||
|
value="{}"
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
"flyoutProps": Object {
|
||||||
|
"aria-labelledby": "triggerConditionFlyout",
|
||||||
|
"maxWidth": 500,
|
||||||
|
"size": "m",
|
||||||
|
},
|
||||||
|
"header": <EuiTitle
|
||||||
|
size="m"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"fontSize": "25px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<h2>
|
||||||
|
<strong>
|
||||||
|
Trigger condition
|
||||||
|
</strong>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>,
|
||||||
|
"headerProps": Object {
|
||||||
|
"hasBorder": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Flyouts from './index';
|
||||||
|
|
||||||
|
describe('Flyouts.message', () => {
|
||||||
|
test('generates message JSON', () => {
|
||||||
|
const json = Flyouts.message();
|
||||||
|
expect(json).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Flyouts.messageFrequency', () => {
|
||||||
|
test('generates message JSON', () => {
|
||||||
|
const json = Flyouts.messageFrequency();
|
||||||
|
expect(json).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Flyouts.triggerCondition', () => {
|
||||||
|
test('generates message JSON', () => {
|
||||||
|
const json = Flyouts.triggerCondition({});
|
||||||
|
expect(json).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import message from './message';
|
||||||
|
import messageFrequency from './messageFrequency';
|
||||||
|
import triggerCondition from './triggerCondition';
|
||||||
|
|
||||||
|
const Flyouts = {
|
||||||
|
messageFrequency,
|
||||||
|
message,
|
||||||
|
triggerCondition,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Flyouts;
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { EuiLink, EuiText, EuiTitle } from '@elastic/eui';
|
||||||
|
import { URL } from '../../../utils/constants';
|
||||||
|
|
||||||
|
const message = () => ({
|
||||||
|
flyoutProps: {
|
||||||
|
'aria-labelledby': 'messageFlyout',
|
||||||
|
maxWidth: 500,
|
||||||
|
size: 'm',
|
||||||
|
},
|
||||||
|
headerProps: { hasBorder: true },
|
||||||
|
header: (
|
||||||
|
<EuiTitle size="m" style={{ fontSize: '25px' }}>
|
||||||
|
<h2>
|
||||||
|
<strong>Message</strong>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>
|
||||||
|
),
|
||||||
|
body: (
|
||||||
|
<EuiText style={{ fontSize: '14px' }}>
|
||||||
|
<p>
|
||||||
|
{`You have access to a "ctx" variable in your painless scripts and action mustache templates.`}
|
||||||
|
</p>
|
||||||
|
<h3>Learn More</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<EuiLink target="_blank" href={URL.MUSTACHE}>
|
||||||
|
HTML Templates with Mustache.js
|
||||||
|
</EuiLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</EuiText>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default message;
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { EuiText, EuiTitle } from '@elastic/eui';
|
||||||
|
|
||||||
|
const messageFrequency = () => ({
|
||||||
|
flyoutProps: {
|
||||||
|
'aria-labelledby': 'messageFrequencyFlyout',
|
||||||
|
maxWidth: 500,
|
||||||
|
size: 'm',
|
||||||
|
},
|
||||||
|
headerProps: { hasBorder: true },
|
||||||
|
header: (
|
||||||
|
<EuiTitle size="m" style={{ fontSize: '25px' }}>
|
||||||
|
<h2>
|
||||||
|
<strong>Message frequency</strong>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>
|
||||||
|
),
|
||||||
|
body: (
|
||||||
|
<EuiText style={{ fontSize: '14px' }}>
|
||||||
|
<p>
|
||||||
|
Specify message frequency to limit the number of notifications you receive within a given
|
||||||
|
span of time. This setting is especially useful for low severity trigger conditions.
|
||||||
|
</p>
|
||||||
|
<p>Consider the following example:</p>
|
||||||
|
<ul>
|
||||||
|
<li>A trigger condition is met.</li>
|
||||||
|
<li>The monitor sends a message</li>
|
||||||
|
<li>Message frequency is set to 10 minutes.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
For the next 10 minutes, even if a trigger condition is met dozens of times, the monitor
|
||||||
|
sends no additional messages. If the trigger condition is met 11 minutes later, the monitor
|
||||||
|
sends another message.
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messageFrequency;
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { EuiCodeBlock, EuiCodeEditor, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
||||||
|
|
||||||
|
const CONTEXT_VARIABLES = JSON.stringify(
|
||||||
|
{
|
||||||
|
monitor: '...',
|
||||||
|
trigger: '...',
|
||||||
|
results: '...',
|
||||||
|
periodStart: '...',
|
||||||
|
periodEnd: '...',
|
||||||
|
alert: '...',
|
||||||
|
error: '...',
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
4
|
||||||
|
);
|
||||||
|
|
||||||
|
const triggerCondition = context => ({
|
||||||
|
flyoutProps: {
|
||||||
|
'aria-labelledby': 'triggerConditionFlyout',
|
||||||
|
maxWidth: 500,
|
||||||
|
size: 'm',
|
||||||
|
},
|
||||||
|
headerProps: { hasBorder: true },
|
||||||
|
header: (
|
||||||
|
<EuiTitle size="m" style={{ fontSize: '25px' }}>
|
||||||
|
<h2>
|
||||||
|
<strong>Trigger condition</strong>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>
|
||||||
|
),
|
||||||
|
body: (
|
||||||
|
<div>
|
||||||
|
<EuiText style={{ fontSize: '14px' }}>
|
||||||
|
<p>You have access to a "ctx" variable in your painless scripts</p>
|
||||||
|
<p>
|
||||||
|
Below shows a quick JSON example of what's available under the "ctx" variable along with
|
||||||
|
the actual results (where possible) for you to reference.
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
|
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
|
||||||
|
<EuiCodeBlock language="json">{CONTEXT_VARIABLES}</EuiCodeBlock>
|
||||||
|
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
|
||||||
|
<EuiCodeEditor
|
||||||
|
mode="json"
|
||||||
|
theme="github"
|
||||||
|
width="100%"
|
||||||
|
height="700px"
|
||||||
|
readOnly
|
||||||
|
value={JSON.stringify(context, null, 4)}
|
||||||
|
setOptions={{ fontSize: '12px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default triggerCondition;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Flyout from './Flyout';
|
||||||
|
|
||||||
|
export default Flyout;
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiCheckbox } from '@elastic/eui';
|
||||||
|
|
||||||
|
import FormikInputWrapper from '../FormikInputWrapper';
|
||||||
|
import FormikFormRow from '../FormikFormRow';
|
||||||
|
|
||||||
|
const FormikCheckbox = ({
|
||||||
|
name,
|
||||||
|
formRow = false,
|
||||||
|
fieldProps = {},
|
||||||
|
rowProps = {},
|
||||||
|
inputProps = {},
|
||||||
|
}) => (
|
||||||
|
<FormikInputWrapper
|
||||||
|
name={name}
|
||||||
|
fieldProps={fieldProps}
|
||||||
|
render={({ field, form }) =>
|
||||||
|
formRow ? (
|
||||||
|
<FormikFormRow name={name} form={form} rowProps={rowProps}>
|
||||||
|
<FieldCheckbox name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
</FormikFormRow>
|
||||||
|
) : (
|
||||||
|
<FieldCheckbox name={name} field={field} inputProps={inputProps} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const FieldCheckbox = ({ name, field: { value, ...rest }, inputProps }) => (
|
||||||
|
<EuiCheckbox name={name} id={name} checked={value} {...inputProps} {...rest} />
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikCheckbox.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
formRow: PropTypes.bool,
|
||||||
|
fieldProps: PropTypes.object,
|
||||||
|
rowProps: PropTypes.object,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
FieldCheckbox.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
inputProps: PropTypes.object.isRequired,
|
||||||
|
field: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikCheckbox;
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import FormikCheckbox from './FormikCheckbox';
|
||||||
|
|
||||||
|
describe('FormikCheckbox', () => {
|
||||||
|
test('render formRow', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikCheckbox name="testing" formRow />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikCheckbox name="testing" />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FormikCheckbox render 1`] = `
|
||||||
|
<div
|
||||||
|
class="euiCheckbox euiCheckbox--noLabel"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="euiCheckbox__input"
|
||||||
|
id="testing"
|
||||||
|
name="testing"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="euiCheckbox__square"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`FormikCheckbox render formRow 1`] = `
|
||||||
|
<div
|
||||||
|
class="euiFormRow"
|
||||||
|
id="testing-form-row-row"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFormRow__fieldWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiCheckbox euiCheckbox--noLabel"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="euiCheckbox__input"
|
||||||
|
id="testing"
|
||||||
|
name="testing"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="euiCheckbox__square"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikCheckbox from './FormikCheckbox';
|
||||||
|
|
||||||
|
export default FormikCheckbox;
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiCodeEditor } from '@elastic/eui';
|
||||||
|
|
||||||
|
import FormikInputWrapper from '../FormikInputWrapper';
|
||||||
|
import FormikFormRow from '../FormikFormRow';
|
||||||
|
|
||||||
|
const FormikCodeEditor = ({
|
||||||
|
name,
|
||||||
|
formRow = false,
|
||||||
|
fieldProps = {},
|
||||||
|
rowProps = {},
|
||||||
|
inputProps = {},
|
||||||
|
}) => (
|
||||||
|
<FormikInputWrapper
|
||||||
|
name={name}
|
||||||
|
fieldProps={fieldProps}
|
||||||
|
render={({ field, form }) =>
|
||||||
|
formRow ? (
|
||||||
|
<FormikFormRow name={name} form={form} rowProps={rowProps}>
|
||||||
|
<CodeEditor name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
</FormikFormRow>
|
||||||
|
) : (
|
||||||
|
<CodeEditor name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const CodeEditor = ({ name, form, field, inputProps: { onBlur, onChange, ...rest } }) => (
|
||||||
|
<EuiCodeEditor
|
||||||
|
id={name}
|
||||||
|
onChange={string => {
|
||||||
|
onChange(string, field, form);
|
||||||
|
}}
|
||||||
|
onBlur={e => {
|
||||||
|
onBlur(e, field, form);
|
||||||
|
}}
|
||||||
|
value={field.value}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikCodeEditor.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
formRow: PropTypes.bool,
|
||||||
|
fieldProps: PropTypes.object,
|
||||||
|
rowProps: PropTypes.object,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeEditor.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
inputProps: PropTypes.object.isRequired,
|
||||||
|
form: PropTypes.object.isRequired,
|
||||||
|
field: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikCodeEditor;
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import FormikCodeEditor from './FormikCodeEditor';
|
||||||
|
|
||||||
|
// FIXME: This has an issue where EuiCodeEditor is generating a random HTML id and failing snapshot test
|
||||||
|
|
||||||
|
describe.skip('FormikCodeEditor', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikCodeEditor name="testing" />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikCodeEditor from './FormikCodeEditor';
|
||||||
|
|
||||||
|
export default FormikCodeEditor;
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiComboBox } from '@elastic/eui';
|
||||||
|
|
||||||
|
import FormikInputWrapper from '../FormikInputWrapper';
|
||||||
|
import FormikFormRow from '../FormikFormRow';
|
||||||
|
|
||||||
|
const FormikComboBox = ({
|
||||||
|
name,
|
||||||
|
formRow = false,
|
||||||
|
fieldProps = {},
|
||||||
|
rowProps = {},
|
||||||
|
inputProps = {},
|
||||||
|
}) => (
|
||||||
|
<FormikInputWrapper
|
||||||
|
name={name}
|
||||||
|
fieldProps={fieldProps}
|
||||||
|
render={({ field, form }) =>
|
||||||
|
formRow ? (
|
||||||
|
<FormikFormRow name={name} form={form} rowProps={rowProps}>
|
||||||
|
<ComboBox name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
</FormikFormRow>
|
||||||
|
) : (
|
||||||
|
<ComboBox name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ComboBox = ({
|
||||||
|
name,
|
||||||
|
form,
|
||||||
|
field,
|
||||||
|
inputProps: { onBlur, onChange, onCreateOption, ...rest },
|
||||||
|
}) => (
|
||||||
|
<EuiComboBox
|
||||||
|
name={name}
|
||||||
|
id={name}
|
||||||
|
onChange={
|
||||||
|
typeof onChange === 'function'
|
||||||
|
? options => {
|
||||||
|
onChange(options, field, form);
|
||||||
|
}
|
||||||
|
: onChange
|
||||||
|
}
|
||||||
|
onCreateOption={
|
||||||
|
typeof onCreateOption === 'function'
|
||||||
|
? value => {
|
||||||
|
onCreateOption(value, field, form);
|
||||||
|
}
|
||||||
|
: onCreateOption
|
||||||
|
}
|
||||||
|
onBlur={
|
||||||
|
typeof onBlur === 'function'
|
||||||
|
? e => {
|
||||||
|
onBlur(e, field, form);
|
||||||
|
}
|
||||||
|
: onBlur
|
||||||
|
}
|
||||||
|
selectedOptions={field.value}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikComboBox.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
formRow: PropTypes.bool,
|
||||||
|
fieldProps: PropTypes.object,
|
||||||
|
rowProps: PropTypes.object,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
ComboBox.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
inputProps: PropTypes.object.isRequired,
|
||||||
|
form: PropTypes.object.isRequired,
|
||||||
|
field: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikComboBox;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import FormikComboBox from './FormikComboBox';
|
||||||
|
|
||||||
|
describe.skip('FormikComboBox', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikComboBox name="testing" />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikComboBox from './FormikComboBox';
|
||||||
|
|
||||||
|
export default FormikComboBox;
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiFieldNumber } from '@elastic/eui';
|
||||||
|
|
||||||
|
import FormikInputWrapper from '../FormikInputWrapper';
|
||||||
|
import FormikFormRow from '../FormikFormRow';
|
||||||
|
|
||||||
|
const FormikFieldNumber = ({
|
||||||
|
name,
|
||||||
|
formRow = false,
|
||||||
|
fieldProps = {},
|
||||||
|
rowProps = {},
|
||||||
|
inputProps = {},
|
||||||
|
}) => (
|
||||||
|
<FormikInputWrapper
|
||||||
|
name={name}
|
||||||
|
fieldProps={fieldProps}
|
||||||
|
render={({ field, form }) =>
|
||||||
|
formRow ? (
|
||||||
|
<FormikFormRow name={name} form={form} rowProps={rowProps}>
|
||||||
|
<FieldNumber name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
</FormikFormRow>
|
||||||
|
) : (
|
||||||
|
<FieldNumber name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const FieldNumber = ({ name, form, field, inputProps: { onChange, isInvalid, ...rest } }) => (
|
||||||
|
<EuiFieldNumber
|
||||||
|
{...field}
|
||||||
|
{...rest}
|
||||||
|
onChange={e => (typeof onChange === 'function' ? onChange(e, field, form) : field.onChange(e))}
|
||||||
|
isInvalid={typeof isInvalid === 'function' ? isInvalid(name, form) : isInvalid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikFieldNumber.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
formRow: PropTypes.bool,
|
||||||
|
fieldProps: PropTypes.object,
|
||||||
|
rowProps: PropTypes.object,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
FieldNumber.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
inputProps: PropTypes.object.isRequired,
|
||||||
|
form: PropTypes.object.isRequired,
|
||||||
|
field: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikFieldNumber;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import FormikFieldNumber from './FormikFieldNumber';
|
||||||
|
|
||||||
|
describe('FormikFieldNumber', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikFieldNumber name="testing" />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FormikFieldNumber renders 1`] = `
|
||||||
|
<div
|
||||||
|
class="euiFormControlLayout"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFormControlLayout__childrenWrapper"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="euiFieldNumber"
|
||||||
|
name="testing"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikFieldNumber from './FormikFieldNumber';
|
||||||
|
|
||||||
|
export default FormikFieldNumber;
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiFieldPassword } from '@elastic/eui';
|
||||||
|
|
||||||
|
import FormikInputWrapper from '../FormikInputWrapper';
|
||||||
|
import FormikFormRow from '../FormikFormRow';
|
||||||
|
|
||||||
|
const FormikFieldPassword = ({
|
||||||
|
name,
|
||||||
|
formRow = false,
|
||||||
|
fieldProps = {},
|
||||||
|
rowProps = {},
|
||||||
|
inputProps = {},
|
||||||
|
}) => (
|
||||||
|
<FormikInputWrapper
|
||||||
|
name={name}
|
||||||
|
fieldProps={fieldProps}
|
||||||
|
render={({ field, form }) =>
|
||||||
|
formRow ? (
|
||||||
|
<FormikFormRow name={name} form={form} rowProps={rowProps}>
|
||||||
|
<FieldPassword name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
</FormikFormRow>
|
||||||
|
) : (
|
||||||
|
<FieldPassword name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const FieldPassword = ({
|
||||||
|
name,
|
||||||
|
form,
|
||||||
|
field,
|
||||||
|
inputProps: { onChange, isInvalid, onFocus, ...rest },
|
||||||
|
}) => (
|
||||||
|
<EuiFieldPassword
|
||||||
|
{...field}
|
||||||
|
{...rest}
|
||||||
|
onChange={e => (typeof onChange === 'function' ? onChange(e, field, form) : field.onChange(e))}
|
||||||
|
onFocus={typeof onFocus === 'function' ? e => onFocus(e, field, form) : onFocus}
|
||||||
|
isInvalid={typeof isInvalid === 'function' ? isInvalid(name, form) : isInvalid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikFieldPassword.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
formRow: PropTypes.bool,
|
||||||
|
fieldProps: PropTypes.object,
|
||||||
|
rowProps: PropTypes.object,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
FieldPassword.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
inputProps: PropTypes.object.isRequired,
|
||||||
|
form: PropTypes.object.isRequired,
|
||||||
|
field: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikFieldPassword;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import FormikFieldPassword from './FormikFieldPassword';
|
||||||
|
|
||||||
|
describe('FormikFieldPassword', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikFieldPassword name="testing" />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FormikFieldPassword renders 1`] = `
|
||||||
|
<div
|
||||||
|
class="euiFormControlLayout"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFormControlLayout__childrenWrapper"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="euiFieldPassword"
|
||||||
|
name="testing"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="euiFormControlLayoutIcons"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="euiFormControlLayoutCustomIcon"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
EuiIconMock
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikFieldPassword from './FormikFieldPassword';
|
||||||
|
|
||||||
|
export default FormikFieldPassword;
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiRadio } from '@elastic/eui';
|
||||||
|
|
||||||
|
import FormikInputWrapper from '../FormikInputWrapper';
|
||||||
|
import FormikFormRow from '../FormikFormRow';
|
||||||
|
|
||||||
|
const FormikFieldRadio = ({
|
||||||
|
name,
|
||||||
|
formRow = false,
|
||||||
|
fieldProps = {},
|
||||||
|
rowProps = {},
|
||||||
|
inputProps = {},
|
||||||
|
}) => (
|
||||||
|
<FormikInputWrapper
|
||||||
|
name={name}
|
||||||
|
fieldProps={fieldProps}
|
||||||
|
render={({ field, form }) =>
|
||||||
|
formRow ? (
|
||||||
|
<FormikFormRow name={name} form={form} rowProps={rowProps}>
|
||||||
|
<FieldRadio name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
</FormikFormRow>
|
||||||
|
) : (
|
||||||
|
<FieldRadio name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const FieldRadio = ({ name, form, field, inputProps: { onChange, ...rest } }) => (
|
||||||
|
<EuiRadio
|
||||||
|
{...field}
|
||||||
|
{...rest}
|
||||||
|
onChange={e => (typeof onChange === 'function' ? onChange(e, field, form) : field.onChange(e))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikFieldRadio.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
formRow: PropTypes.bool,
|
||||||
|
fieldProps: PropTypes.object,
|
||||||
|
rowProps: PropTypes.object,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
FieldRadio.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
inputProps: PropTypes.object.isRequired,
|
||||||
|
form: PropTypes.object.isRequired,
|
||||||
|
field: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikFieldRadio;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikFieldRadio from './FormikFieldRadio';
|
||||||
|
|
||||||
|
export default FormikFieldRadio;
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiFieldText } from '@elastic/eui';
|
||||||
|
|
||||||
|
import FormikInputWrapper from '../FormikInputWrapper';
|
||||||
|
import FormikFormRow from '../FormikFormRow';
|
||||||
|
|
||||||
|
const FormikFieldText = ({
|
||||||
|
name,
|
||||||
|
formRow = false,
|
||||||
|
fieldProps = {},
|
||||||
|
rowProps = {},
|
||||||
|
inputProps = {},
|
||||||
|
}) => (
|
||||||
|
<FormikInputWrapper
|
||||||
|
name={name}
|
||||||
|
fieldProps={fieldProps}
|
||||||
|
render={({ field, form }) =>
|
||||||
|
formRow ? (
|
||||||
|
<FormikFormRow name={name} form={form} rowProps={rowProps}>
|
||||||
|
<FieldText name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
</FormikFormRow>
|
||||||
|
) : (
|
||||||
|
<FieldText name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const FieldText = ({
|
||||||
|
name,
|
||||||
|
form,
|
||||||
|
field,
|
||||||
|
inputProps: { onChange, isInvalid, onFocus, ...rest },
|
||||||
|
}) => (
|
||||||
|
<EuiFieldText
|
||||||
|
{...field}
|
||||||
|
{...rest}
|
||||||
|
onChange={e => (typeof onChange === 'function' ? onChange(e, field, form) : field.onChange(e))}
|
||||||
|
onFocus={typeof onFocus === 'function' ? e => onFocus(e, field, form) : onFocus}
|
||||||
|
isInvalid={typeof isInvalid === 'function' ? isInvalid(name, form) : isInvalid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikFieldText.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
formRow: PropTypes.bool,
|
||||||
|
fieldProps: PropTypes.object,
|
||||||
|
rowProps: PropTypes.object,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
FieldText.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
inputProps: PropTypes.object.isRequired,
|
||||||
|
form: PropTypes.object.isRequired,
|
||||||
|
field: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikFieldText;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import FormikFieldText from './FormikFieldText';
|
||||||
|
|
||||||
|
describe('FormikFieldText', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikFieldText name="testing" />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FormikFieldText renders 1`] = `
|
||||||
|
<div
|
||||||
|
class="euiFormControlLayout"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFormControlLayout__childrenWrapper"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="euiFieldText"
|
||||||
|
name="testing"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikFieldText from './FormikFieldText';
|
||||||
|
|
||||||
|
export default FormikFieldText;
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiFormRow } from '@elastic/eui';
|
||||||
|
|
||||||
|
const FormikFormRow = ({ children, form, name, rowProps: { isInvalid, error, ...rest } }) => (
|
||||||
|
<EuiFormRow
|
||||||
|
id={`${name}-form-row`}
|
||||||
|
isInvalid={typeof isInvalid === 'function' ? isInvalid(name, form) : isInvalid}
|
||||||
|
error={typeof error === 'function' ? error(name, form) : error}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</EuiFormRow>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikFormRow.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
rowProps: PropTypes.object.isRequired,
|
||||||
|
form: PropTypes.object.isRequired,
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikFormRow;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
|
||||||
|
import FormikFormRow from './FormikFormRow';
|
||||||
|
|
||||||
|
describe('FormikFormRow', () => {
|
||||||
|
const Child = () => <div id="mine">child</div>;
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<FormikFormRow name="testing" rowProps={{}} form={{}}>
|
||||||
|
<Child />
|
||||||
|
</FormikFormRow>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FormikFormRow renders 1`] = `
|
||||||
|
<div
|
||||||
|
class="euiFormRow"
|
||||||
|
id="testing-form-row-row"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFormRow__fieldWrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="mine"
|
||||||
|
>
|
||||||
|
child
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikFormRow from './FormikFormRow';
|
||||||
|
|
||||||
|
export default FormikFormRow;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Field } from 'formik';
|
||||||
|
|
||||||
|
const FormikInputWrapper = ({ name, fieldProps, render }) => (
|
||||||
|
<Field name={name} {...fieldProps}>
|
||||||
|
{({ field, form }) => render({ field, form })}
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikInputWrapper.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
fieldProps: PropTypes.object.isRequired,
|
||||||
|
render: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikInputWrapper;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import FormikInputWrapper from './FormikInputWrapper';
|
||||||
|
|
||||||
|
describe('FormikInputWrapper', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikInputWrapper name="testing" fieldProps={{}} render={() => <div>test</div>} />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FormikInputWrapper renders 1`] = `
|
||||||
|
<div>
|
||||||
|
test
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikInputWrapper from './FormikInputWrapper';
|
||||||
|
|
||||||
|
export default FormikInputWrapper;
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiSelect } from '@elastic/eui';
|
||||||
|
|
||||||
|
import FormikInputWrapper from '../FormikInputWrapper';
|
||||||
|
import FormikFormRow from '../FormikFormRow';
|
||||||
|
|
||||||
|
const FormikSelect = ({
|
||||||
|
name,
|
||||||
|
formRow = false,
|
||||||
|
fieldProps = {},
|
||||||
|
rowProps = {},
|
||||||
|
inputProps = {},
|
||||||
|
}) => (
|
||||||
|
<FormikInputWrapper
|
||||||
|
name={name}
|
||||||
|
fieldProps={fieldProps}
|
||||||
|
render={({ field, form }) =>
|
||||||
|
formRow ? (
|
||||||
|
<FormikFormRow name={name} form={form} rowProps={rowProps}>
|
||||||
|
<FieldSelect name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
</FormikFormRow>
|
||||||
|
) : (
|
||||||
|
<FieldSelect name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const FieldSelect = ({ name, field, form, inputProps: { onChange, isInvalid, ...rest } }) => (
|
||||||
|
<EuiSelect
|
||||||
|
name={name}
|
||||||
|
id={name}
|
||||||
|
{...field}
|
||||||
|
{...rest}
|
||||||
|
onChange={e => (typeof onChange === 'function' ? onChange(e, field, form) : field.onChange(e))}
|
||||||
|
isInvalid={typeof isInvalid === 'function' ? isInvalid(name, form) : isInvalid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikSelect.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
formRow: PropTypes.bool,
|
||||||
|
fieldProps: PropTypes.object,
|
||||||
|
rowProps: PropTypes.object,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
FieldSelect.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
form: PropTypes.object.isRequired,
|
||||||
|
inputProps: PropTypes.object.isRequired,
|
||||||
|
field: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikSelect;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import FormikSelect from './FormikSelect';
|
||||||
|
|
||||||
|
describe('FormikSelect', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikSelect name="testing" inputProps={{ options: [{ value: 'test', text: 'test' }] }} />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FormikSelect renders 1`] = `
|
||||||
|
<div
|
||||||
|
class="euiFormControlLayout"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFormControlLayout__childrenWrapper"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
class="euiSelect"
|
||||||
|
id="testing"
|
||||||
|
name="testing"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value="test"
|
||||||
|
>
|
||||||
|
test
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div
|
||||||
|
class="euiFormControlLayoutIcons euiFormControlLayoutIcons--right"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="euiFormControlLayoutCustomIcon"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
EuiIconMock
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikSelect from './FormikSelect';
|
||||||
|
|
||||||
|
export default FormikSelect;
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiSwitch } from '@elastic/eui';
|
||||||
|
|
||||||
|
import FormikInputWrapper from '../FormikInputWrapper';
|
||||||
|
import FormikFormRow from '../FormikFormRow';
|
||||||
|
|
||||||
|
const FormikSwitch = ({
|
||||||
|
name,
|
||||||
|
formRow = false,
|
||||||
|
fieldProps = {},
|
||||||
|
rowProps = {},
|
||||||
|
inputProps = {},
|
||||||
|
}) => (
|
||||||
|
<FormikInputWrapper
|
||||||
|
name={name}
|
||||||
|
fieldProps={fieldProps}
|
||||||
|
render={({ field, form }) =>
|
||||||
|
formRow ? (
|
||||||
|
<FormikFormRow name={name} form={form} rowProps={rowProps}>
|
||||||
|
<FieldSwitch name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
</FormikFormRow>
|
||||||
|
) : (
|
||||||
|
<FieldSwitch name={name} field={field} inputProps={inputProps} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const FieldSwitch = ({ name, field: { value, ...rest }, inputProps }) => (
|
||||||
|
<EuiSwitch name={name} id={name} checked={value} {...inputProps} {...rest} />
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikSwitch.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
formRow: PropTypes.bool,
|
||||||
|
fieldProps: PropTypes.object,
|
||||||
|
rowProps: PropTypes.object,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
FieldSwitch.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
inputProps: PropTypes.object.isRequired,
|
||||||
|
field: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikSwitch;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import FormikSwitch from './FormikSwitch';
|
||||||
|
|
||||||
|
describe('FormikSwitch', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikSwitch name="testing" />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FormikSwitch renders 1`] = `
|
||||||
|
<div
|
||||||
|
class="euiSwitch"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-checked="false"
|
||||||
|
aria-labelledby="generated-id"
|
||||||
|
class="euiSwitch__button"
|
||||||
|
id="testing"
|
||||||
|
name="testing"
|
||||||
|
role="switch"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="euiSwitch__body"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="euiSwitch__thumb"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="euiSwitch__track"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
EuiIconMock
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
EuiIconMock
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<span
|
||||||
|
class="euiSwitch__label"
|
||||||
|
id="generated-id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikSwitch from './FormikSwitch';
|
||||||
|
|
||||||
|
export default FormikSwitch;
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiTextArea } from '@elastic/eui';
|
||||||
|
import FormikInputWrapper from '../FormikInputWrapper';
|
||||||
|
import FormikFormRow from '../FormikFormRow';
|
||||||
|
|
||||||
|
const FormikTextArea = ({
|
||||||
|
name,
|
||||||
|
formRow = false,
|
||||||
|
fieldProps = {},
|
||||||
|
rowProps = {},
|
||||||
|
inputProps = {},
|
||||||
|
}) => (
|
||||||
|
<FormikInputWrapper
|
||||||
|
name={name}
|
||||||
|
fieldProps={fieldProps}
|
||||||
|
render={({ field, form }) =>
|
||||||
|
formRow ? (
|
||||||
|
<FormikFormRow name={name} form={form} rowProps={rowProps}>
|
||||||
|
<TextArea name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
</FormikFormRow>
|
||||||
|
) : (
|
||||||
|
<TextArea name={name} form={form} field={field} inputProps={inputProps} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const TextArea = ({ name, form, field, inputProps: { isInvalid, ...rest } }) => (
|
||||||
|
<EuiTextArea
|
||||||
|
isInvalid={typeof isInvalid === 'function' ? isInvalid(name, form) : isInvalid}
|
||||||
|
{...field}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
FormikTextArea.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
formRow: PropTypes.bool,
|
||||||
|
fieldProps: PropTypes.object,
|
||||||
|
rowProps: PropTypes.object,
|
||||||
|
inputProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
TextArea.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
inputProps: PropTypes.object.isRequired,
|
||||||
|
form: PropTypes.object.isRequired,
|
||||||
|
field: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormikTextArea;
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
|
||||||
|
import FormikTextArea from './FormikTextArea';
|
||||||
|
|
||||||
|
describe('FormikTextArea', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = (
|
||||||
|
<Formik>
|
||||||
|
<FormikTextArea name="testing" />
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FormikTextArea renders 1`] = `
|
||||||
|
<textarea
|
||||||
|
class="euiTextArea euiTextArea--resizeVertical"
|
||||||
|
name="testing"
|
||||||
|
rows="6"
|
||||||
|
/>
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikTextArea from './FormikTextArea';
|
||||||
|
|
||||||
|
export default FormikTextArea;
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import FormikFieldText from './FormikFieldText';
|
||||||
|
import FormikFormRow from './FormikFormRow';
|
||||||
|
import FormikInputWrapper from './FormikInputWrapper';
|
||||||
|
import FormikTextArea from './FormikTextArea';
|
||||||
|
import FormikSwitch from './FormikSwitch';
|
||||||
|
import FormikCheckbox from './FormikCheckbox';
|
||||||
|
import FormikSelect from './FormikSelect';
|
||||||
|
import FormikFieldNumber from './FormikFieldNumber';
|
||||||
|
import FormikCodeEditor from './FormikCodeEditor';
|
||||||
|
import FormikComboBox from './FormikComboBox';
|
||||||
|
import FormikFieldPassword from './FormikFieldPassword';
|
||||||
|
import FormikFieldRadio from './FormikFieldRadio';
|
||||||
|
|
||||||
|
export {
|
||||||
|
FormikComboBox,
|
||||||
|
FormikSwitch,
|
||||||
|
FormikCheckbox,
|
||||||
|
FormikSelect,
|
||||||
|
FormikFieldNumber,
|
||||||
|
FormikFieldText,
|
||||||
|
FormikFormRow,
|
||||||
|
FormikInputWrapper,
|
||||||
|
FormikTextArea,
|
||||||
|
FormikCodeEditor,
|
||||||
|
FormikFieldPassword,
|
||||||
|
FormikFieldRadio,
|
||||||
|
};
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiHorizontalRule, EuiText, EuiTitle } from '@elastic/eui';
|
||||||
|
|
||||||
|
const DEFAULT_PROPS = { size: 'xs', style: { paddingLeft: '10px' } };
|
||||||
|
const SubHeader = ({
|
||||||
|
description,
|
||||||
|
descriptionProps = DEFAULT_PROPS,
|
||||||
|
horizontalRuleMargin = 'xs',
|
||||||
|
title,
|
||||||
|
titleProps = DEFAULT_PROPS,
|
||||||
|
}) => (
|
||||||
|
<Fragment>
|
||||||
|
<EuiTitle {...titleProps}>{title}</EuiTitle>
|
||||||
|
<EuiHorizontalRule margin={horizontalRuleMargin} />
|
||||||
|
<EuiText {...descriptionProps}>{description}</EuiText>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
SubHeader.propTypes = {
|
||||||
|
description: PropTypes.node.isRequired,
|
||||||
|
descriptionProps: PropTypes.object,
|
||||||
|
horizontalRuleMargin: PropTypes.string,
|
||||||
|
title: PropTypes.node.isRequired,
|
||||||
|
titleProps: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubHeader;
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
|
||||||
|
import SubHeader from './SubHeader';
|
||||||
|
|
||||||
|
describe('SubHeader', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const component = <SubHeader description={<div>description</div>} title={<div>title</div>} />;
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`SubHeader renders 1`] = `
|
||||||
|
Array [
|
||||||
|
<div
|
||||||
|
class="euiTitle euiTitle--xsmall"
|
||||||
|
style="padding-left:10px"
|
||||||
|
>
|
||||||
|
title
|
||||||
|
</div>,
|
||||||
|
<hr
|
||||||
|
class="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginXSmall"
|
||||||
|
/>,
|
||||||
|
<div
|
||||||
|
class="euiText euiText--extraSmall"
|
||||||
|
style="padding-left:10px"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
description
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
]
|
||||||
|
`;
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import SubHeader from './SubHeader';
|
||||||
|
|
||||||
|
export default SubHeader;
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32px" height="32px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 57.1 (83088) - https://sketch.com -->
|
||||||
|
<title>Alerting</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="Alerting" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Group-2" transform="translate(4.000000, 0.500000)">
|
||||||
|
<g id="Group-13">
|
||||||
|
<path d="M9.49121094,20.5 C9.49121094,19.1192881 8.37192281,18 6.99121094,18 C5.61049906,18 4.49121094,19.1192881 4.49121094,20.5" id="Oval-Copy-80" stroke="#007D73" transform="translate(6.991211, 19.250000) scale(1, -1) translate(-6.991211, -19.250000) "></path>
|
||||||
|
<path d="M12.9912109,9.1 C12.9912109,6.0072054 10.3049194,3.5 6.99121094,3.5 C3.67750244,3.5 0.991210938,6.0072054 0.991210938,9.1 C0.991210938,12.8807292 0.660807292,15.6807292 0,17.5 L13.9875,17.5 C12.9912109,14.7215104 12.9912109,12.1927946 12.9912109,9.1 Z" id="Oval" stroke="#333741"></path>
|
||||||
|
<circle id="Oval-Copy-67" stroke="#333741" cx="6.99121094" cy="2" r="1.5"></circle>
|
||||||
|
</g>
|
||||||
|
<path d="M2.5,14.5 L11.5,14.5" id="Line-9" stroke="#007D73" stroke-linecap="square"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,61 @@
|
||||||
|
import Main from './pages/Main';
|
||||||
|
import { CoreContext } from './utils/CoreContext';
|
||||||
|
import {Fetch} from '../../components/kibana/core/public/http/fetch';
|
||||||
|
import {Router} from 'react-router-dom';
|
||||||
|
import {useMemo} from 'react';
|
||||||
|
import {ScopedHistory} from '../../components/kibana/core/public/application/scoped_history';
|
||||||
|
import {notification} from 'antd';
|
||||||
|
import {connect} from 'dva'
|
||||||
|
|
||||||
|
const httpClient = new Fetch({
|
||||||
|
basePath:{
|
||||||
|
get: () => '',
|
||||||
|
prepend: (url) => url,
|
||||||
|
remove: (url) => url,
|
||||||
|
serverBasePath: '/elasticsearch',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const notifications = {
|
||||||
|
toasts: {
|
||||||
|
addDanger: ({title, text, toastLifeTimeMs})=>{
|
||||||
|
notification.warning({
|
||||||
|
message: title,
|
||||||
|
description: text,
|
||||||
|
duration: toastLifeTimeMs/1000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AlertingUI = (props)=>{
|
||||||
|
if(!props.selectedCluster.id){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
useMemo(()=>{
|
||||||
|
httpClient.getServerBasePath = ()=>{
|
||||||
|
return '/api/elasticsearch/'+ props.selectedCluster.id;
|
||||||
|
}
|
||||||
|
}, [props.selectedCluster]);
|
||||||
|
const isDarkMode = false;
|
||||||
|
const history = useMemo(()=>{
|
||||||
|
return new ScopedHistory(props.history, '/alerting');
|
||||||
|
}, [props.history])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CoreContext.Provider
|
||||||
|
value={{ http: httpClient, isDarkMode, notifications: notifications }}
|
||||||
|
>
|
||||||
|
<Router history={history}>
|
||||||
|
<div style={{background:'#fff'}}>
|
||||||
|
<Main title="Alerting" {...props} />
|
||||||
|
</div>
|
||||||
|
</Router>
|
||||||
|
</CoreContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(({
|
||||||
|
global
|
||||||
|
})=>({
|
||||||
|
selectedCluster: global.selectedCluster,
|
||||||
|
}))(AlertingUI)
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-horizontal-rule {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-action {
|
||||||
|
padding: 10px 0px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
.euiAccordion__button {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-action:last-child {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-separator {
|
||||||
|
background-image: linear-gradient(#D3DAE6, #D3DAE6);
|
||||||
|
background-size: 100% 1px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-manage-email {
|
||||||
|
min-width: 800px;
|
||||||
|
height: 75vh;
|
||||||
|
max-height: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.euiTextArea.read-only-text-area {
|
||||||
|
padding-left: 12px;
|
||||||
|
border-color: initial;
|
||||||
|
box-shadow: 0 1px 1px -1px rgba(153, 153, 153, 0.2), 0 3px 2px -2px rgba(153, 153, 153, 0.2),
|
||||||
|
inset 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kibana itself has an old version of EUI stylesheets that is being applied to our upgraded EUI library.
|
||||||
|
* Our own EUI dependency is overwriting the Kibana EUI dependency stylesheets, but any CSS properties that aren't
|
||||||
|
* included in the local dependency aren't overwritten, so some extra styles are sometimes applied such as this case:
|
||||||
|
* .euiSwitch .euiSwitch__thumb has a width/height set at 24px in older version which breaks the styling of the newer version.
|
||||||
|
* */
|
||||||
|
|
||||||
|
.euiFormRow.euiFormRow--hasEmptyLabelSpace {
|
||||||
|
padding-top: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.euiModal--confirmation {
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.euiModal {
|
||||||
|
padding: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.euiRangePicker--popper {
|
||||||
|
left: unset !important;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.euiDatePickerRange__icon {
|
||||||
|
flex: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.euiPaginationButton-isActive:focus {
|
||||||
|
color: #0079a5 !important;
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiText, EuiSpacer } from '@elastic/eui';
|
||||||
|
import {
|
||||||
|
Chart,
|
||||||
|
Axis,
|
||||||
|
LineSeries,
|
||||||
|
niceTimeFormatter,
|
||||||
|
Settings,
|
||||||
|
Position,
|
||||||
|
LineAnnotation,
|
||||||
|
} from '@elastic/charts';
|
||||||
|
import { ChartContainer } from '../../../../../components/ChartContainer/ChartContainer';
|
||||||
|
import DelayedLoader from '../../../../../components/DelayedLoader';
|
||||||
|
|
||||||
|
export const MAX_DATA_POINTS = 1000;
|
||||||
|
|
||||||
|
const getAxisTitle = (displayGrade, displayConfidence) => {
|
||||||
|
if (displayGrade && displayConfidence) {
|
||||||
|
return 'Anomaly grade / confidence';
|
||||||
|
}
|
||||||
|
return displayGrade ? 'Anomaly grade' : 'Anomaly confidence';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In case of too many anomalies (e.g., high-cardinality detectors), we only keep the max anomalies within
|
||||||
|
* allowed range. Otherwise, return data as they are.
|
||||||
|
* @param {any[]} data The original anomaly result from preview
|
||||||
|
* @returns {any[]} anomalies within allowed range
|
||||||
|
*/
|
||||||
|
export const prepareDataForChart = (data) => {
|
||||||
|
if (data && data.length > MAX_DATA_POINTS) {
|
||||||
|
return sampleMaxAnomalyGrade(data);
|
||||||
|
} else {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caclulate the stride between each step
|
||||||
|
* @param {number} total Total number of preview results
|
||||||
|
* @returns {number} The stride
|
||||||
|
*/
|
||||||
|
const calculateStep = (total) => {
|
||||||
|
return Math.ceil(total / MAX_DATA_POINTS);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick the elememtn with the max anomaly grade within the input array
|
||||||
|
* @param {any[]} anomalies Input array with preview results
|
||||||
|
* @returns The elememtn with the max anomaly grade
|
||||||
|
*/
|
||||||
|
const findAnomalyWithMaxAnomalyGrade = (anomalies) => {
|
||||||
|
let anomalyWithMaxGrade = anomalies[0];
|
||||||
|
for (let i = 1; i < anomalies.length; i++) {
|
||||||
|
if (anomalies[i].anomalyGrade > anomalyWithMaxGrade.anomalyGrade) {
|
||||||
|
anomalyWithMaxGrade = anomalies[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return anomalyWithMaxGrade;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample max anomalies within allowed range
|
||||||
|
* @param {any[]} data The original results from preview
|
||||||
|
* @return {any[]} sampled anomalies
|
||||||
|
*/
|
||||||
|
const sampleMaxAnomalyGrade = (data) => {
|
||||||
|
let sortedData = data.sort((a, b) => (a.plotTime > b.plotTime ? 1 : -1));
|
||||||
|
const step = calculateStep(sortedData.length);
|
||||||
|
let index = 0;
|
||||||
|
const sampledAnomalies = [];
|
||||||
|
while (index < sortedData.length) {
|
||||||
|
const arr = sortedData.slice(index, index + step);
|
||||||
|
sampledAnomalies.push(findAnomalyWithMaxAnomalyGrade(arr));
|
||||||
|
index = index + step;
|
||||||
|
}
|
||||||
|
return sampledAnomalies;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AnomaliesChart = (props) => {
|
||||||
|
const timeFormatter = niceTimeFormatter([props.startDateTime, props.endDateTime]);
|
||||||
|
const preparedAnomalies = prepareDataForChart(props.anomalies);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DelayedLoader isLoading={props.isLoading}>
|
||||||
|
{(showLoader) => (
|
||||||
|
<React.Fragment>
|
||||||
|
{props.showTitle ? (
|
||||||
|
<EuiText size="xs">
|
||||||
|
<strong>{props.title}</strong>
|
||||||
|
</EuiText>
|
||||||
|
) : null}
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
<div>
|
||||||
|
<ChartContainer
|
||||||
|
style={{ height: '300px', width: '100%', opacity: showLoader ? 0.2 : 1 }}
|
||||||
|
>
|
||||||
|
<Chart>
|
||||||
|
{props.showSettings ? (
|
||||||
|
<Settings
|
||||||
|
showLegend
|
||||||
|
legendPosition={Position.Bottom}
|
||||||
|
showLegendDisplayValue={false}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<Axis id="bottom" position="bottom" tickFormat={timeFormatter} />
|
||||||
|
<Axis
|
||||||
|
id="left"
|
||||||
|
title={getAxisTitle(props.displayGrade, props.displayConfidence)}
|
||||||
|
position="left"
|
||||||
|
domain={{ min: 0, max: 1 }}
|
||||||
|
/>
|
||||||
|
{props.annotationData ? (
|
||||||
|
<LineAnnotation
|
||||||
|
annotationId="anomalyAnnotation"
|
||||||
|
domainType="yDomain"
|
||||||
|
dataValues={props.annotationData}
|
||||||
|
style={{
|
||||||
|
line: {
|
||||||
|
strokeWidth: 1.5,
|
||||||
|
stroke: '#f00',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{props.displayGrade ? (
|
||||||
|
<LineSeries
|
||||||
|
id="Anomaly grade"
|
||||||
|
xScaleType="time"
|
||||||
|
yScaleType="linear"
|
||||||
|
xAccessor={'plotTime'}
|
||||||
|
yAccessors={['anomalyGrade']}
|
||||||
|
data={preparedAnomalies}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{props.displayConfidence ? (
|
||||||
|
<LineSeries
|
||||||
|
id="Confidence"
|
||||||
|
xScaleType="time"
|
||||||
|
yScaleType="linear"
|
||||||
|
xAccessor={'plotTime'}
|
||||||
|
yAccessors={['confidence']}
|
||||||
|
data={preparedAnomalies}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Chart>
|
||||||
|
</ChartContainer>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
</DelayedLoader>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
AnomaliesChart.propTypes = {
|
||||||
|
startDateTime: PropTypes.number,
|
||||||
|
endDateTime: PropTypes.number,
|
||||||
|
endDate: PropTypes.number,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
|
showTitle: PropTypes.bool,
|
||||||
|
showSettings: PropTypes.bool,
|
||||||
|
annotationData: PropTypes.array,
|
||||||
|
anomalies: PropTypes.array.isRequired,
|
||||||
|
title: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
AnomaliesChart.defaultProps = {
|
||||||
|
isLoading: false,
|
||||||
|
showTitle: true,
|
||||||
|
showSettings: true,
|
||||||
|
anomalies: undefined,
|
||||||
|
title: 'Sample preview for anomaly score',
|
||||||
|
};
|
||||||
|
|
||||||
|
export { AnomaliesChart };
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { AnomaliesChart, MAX_DATA_POINTS, prepareDataForChart } from './AnomaliesChart';
|
||||||
|
|
||||||
|
const startTime = moment('2018-10-25T09:30:00').valueOf();
|
||||||
|
const endTime = moment('2018-10-29T09:30:00').valueOf();
|
||||||
|
|
||||||
|
const getRandomArbitrary = (min, max) => {
|
||||||
|
return Math.floor(Math.random() * (max - min) + min);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate preview results
|
||||||
|
* @param {Number} startTime Preview start time in epoch milliseconds
|
||||||
|
* @param {Number} endTime Preview end time in epoch milliseconds
|
||||||
|
* @param {Number} count Number of results
|
||||||
|
* @returns {any[]} Generated results
|
||||||
|
*/
|
||||||
|
const createTestData = (startTime, endTime, count) => {
|
||||||
|
const data = [];
|
||||||
|
const interval = 60000;
|
||||||
|
const midInterval = interval / 2;
|
||||||
|
for (let i = 0; i < count - 3; i++) {
|
||||||
|
let startGenerated = getRandomArbitrary(startTime, endTime);
|
||||||
|
data.push({
|
||||||
|
anomalyGrade: 0,
|
||||||
|
confidence: 0,
|
||||||
|
dataEndTime: startGenerated + interval,
|
||||||
|
dataStartTime: startGenerated,
|
||||||
|
detectorId: 'nxEuT3YBdrEXnzbxJ7XZ',
|
||||||
|
plotTime: startGenerated + midInterval,
|
||||||
|
schemaVersion: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// injected 3 anomalies: the beginning, the end, and the middle.
|
||||||
|
data.push({
|
||||||
|
anomalyGrade: 1,
|
||||||
|
confidence: 0.7,
|
||||||
|
dataEndTime: startTime + interval,
|
||||||
|
dataStartTime: startTime,
|
||||||
|
detectorId: 'nxEuT3YBdrEXnzbxJ7XZ',
|
||||||
|
plotTime: startTime + midInterval,
|
||||||
|
schemaVersion: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
anomalyGrade: 0.9,
|
||||||
|
confidence: 0.8,
|
||||||
|
dataEndTime: endTime,
|
||||||
|
dataStartTime: endTime - interval,
|
||||||
|
detectorId: 'nxEuT3YBdrEXnzbxJ7XZ',
|
||||||
|
plotTime: endTime - interval + midInterval,
|
||||||
|
schemaVersion: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mid = startTime + (endTime - startTime) / 2;
|
||||||
|
data.push({
|
||||||
|
anomalyGrade: 0.7,
|
||||||
|
confidence: 0.9,
|
||||||
|
dataEndTime: mid + interval,
|
||||||
|
dataStartTime: mid,
|
||||||
|
detectorId: 'nxEuT3YBdrEXnzbxJ7XZ',
|
||||||
|
plotTime: mid + midInterval,
|
||||||
|
schemaVersion: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('AnomaliesChart', () => {
|
||||||
|
test('renders ', () => {
|
||||||
|
const sampleData = [
|
||||||
|
{
|
||||||
|
anomalyGrade: 0.9983687181753063,
|
||||||
|
anomalyScore: 0.8381447468893426,
|
||||||
|
confidence: 0.42865659282252266,
|
||||||
|
detectorId: 'temp',
|
||||||
|
endTime: 1569097677667,
|
||||||
|
plotTime: 1569097377667,
|
||||||
|
startTime: 1569097077667,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const component = (
|
||||||
|
<AnomaliesChart
|
||||||
|
anomalies={sampleData}
|
||||||
|
startDateTime={startTime}
|
||||||
|
endDateTime={endTime}
|
||||||
|
isLoading={false}
|
||||||
|
displayGrade
|
||||||
|
displayConfidence
|
||||||
|
showTitle
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hc detector trigger definition', () => {
|
||||||
|
let startTime = 1608327992253;
|
||||||
|
let endTime = 1608759992253;
|
||||||
|
const preparedAnomalies = prepareDataForChart(
|
||||||
|
createTestData(startTime, endTime, MAX_DATA_POINTS * 30)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(preparedAnomalies.length).toBeCloseTo(MAX_DATA_POINTS);
|
||||||
|
|
||||||
|
expect(preparedAnomalies[MAX_DATA_POINTS - 1].anomalyGrade).toBeCloseTo(0.9);
|
||||||
|
expect(preparedAnomalies[MAX_DATA_POINTS - 1].confidence).toBeCloseTo(0.8);
|
||||||
|
|
||||||
|
var anomalyNumber = 0;
|
||||||
|
for (let i = 0; i < MAX_DATA_POINTS; i++) {
|
||||||
|
if (preparedAnomalies[i].anomalyGrade > 0) {
|
||||||
|
anomalyNumber++;
|
||||||
|
// we injected an anomaly in the middle. Due to randomness, we cannot predict which one it is.
|
||||||
|
if (i > 0 && i < MAX_DATA_POINTS - 1) {
|
||||||
|
expect(preparedAnomalies[i].anomalyGrade).toBeCloseTo(0.7);
|
||||||
|
expect(preparedAnomalies[i].confidence).toBeCloseTo(0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// injected 3 anomalies
|
||||||
|
expect(anomalyNumber).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('single-stream detector trigger definition', () => {
|
||||||
|
let startTime = 1608327992253;
|
||||||
|
let endTime = 1608759992253;
|
||||||
|
|
||||||
|
let originalPreviewResults = createTestData(startTime, endTime, MAX_DATA_POINTS);
|
||||||
|
// we only consolidate and reduce original data when the input data size is larger than MAX_DATA_POINTS
|
||||||
|
expect(prepareDataForChart(originalPreviewResults)).toBe(originalPreviewResults);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`AnomaliesChart renders 1`] = `
|
||||||
|
Array [
|
||||||
|
<div
|
||||||
|
class="euiText euiText--extraSmall"
|
||||||
|
>
|
||||||
|
<strong>
|
||||||
|
Sample preview for anomaly score
|
||||||
|
</strong>
|
||||||
|
</div>,
|
||||||
|
<div
|
||||||
|
class="euiSpacer euiSpacer--s"
|
||||||
|
/>,
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style="border-radius:5px;padding:10px;border:1px solid #D9D9D9;height:300px;width:100%;opacity:1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="echChart"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="echChartBackground"
|
||||||
|
style="background-color:transparent"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="echChartStatus"
|
||||||
|
data-ech-render-complete="false"
|
||||||
|
data-ech-render-count="0"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="echChartResizer"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="echContainer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
]
|
||||||
|
`;
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { AnomaliesChart } from './AnomaliesChart';
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { EuiEmptyPrompt, EuiButton, EuiText, EuiLoadingChart } from '@elastic/eui';
|
||||||
|
import { KIBANA_AD_PLUGIN } from '../../../../../utils/constants';
|
||||||
|
|
||||||
|
const EmptyFeaturesMessage = props => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderRadius: '5px',
|
||||||
|
padding: '10px',
|
||||||
|
border: '1px solid #D9D9D9',
|
||||||
|
height: '250px',
|
||||||
|
width: '100%',
|
||||||
|
...props.containerStyle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.isLoading ? (
|
||||||
|
<EuiEmptyPrompt style={{ maxWidth: '45em' }} body={<EuiLoadingChart size="xl" />} />
|
||||||
|
) : (
|
||||||
|
<EuiEmptyPrompt
|
||||||
|
style={{ maxWidth: '45em' }}
|
||||||
|
body={
|
||||||
|
<EuiText>
|
||||||
|
No features have been added to this anomaly detector. A feature is a metric that is used
|
||||||
|
for anomaly detection. A detector can discover anomalies across one or more features.
|
||||||
|
</EuiText>
|
||||||
|
}
|
||||||
|
actions={[
|
||||||
|
<EuiButton
|
||||||
|
data-test-subj="createButton"
|
||||||
|
href={`${KIBANA_AD_PLUGIN}#/detectors/${props.detectorId}/features`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Add Feature
|
||||||
|
</EuiButton>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
EmptyFeaturesMessage.propTypes = {
|
||||||
|
detectorId: PropTypes.string,
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
containerStyle: PropTypes.object,
|
||||||
|
};
|
||||||
|
EmptyFeaturesMessage.defaultProps = {
|
||||||
|
detectorId: '',
|
||||||
|
containerStyle: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { EmptyFeaturesMessage };
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'enzyme';
|
||||||
|
import { EmptyFeaturesMessage } from './EmptyFeaturesMessage';
|
||||||
|
|
||||||
|
describe('EmptyFeaturesMessage', () => {
|
||||||
|
test('renders ', () => {
|
||||||
|
const component = <EmptyFeaturesMessage detectorId="tempId" />;
|
||||||
|
expect(render(component)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`EmptyFeaturesMessage renders 1`] = `
|
||||||
|
<div
|
||||||
|
style="border-radius:5px;padding:10px;border:1px solid #D9D9D9;height:250px;width:100%"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiEmptyPrompt"
|
||||||
|
style="max-width:45em"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="euiTextColor euiTextColor--subdued"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiText euiText--medium"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiText euiText--medium"
|
||||||
|
>
|
||||||
|
No features have been added to this anomaly detector. A feature is a metric that is used for anomaly detection. A detector can discover anomalies across one or more features.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="euiSpacer euiSpacer--l"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="euiSpacer euiSpacer--s"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="euiFlexGroup euiFlexGroup--gutterMedium euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentCenter euiFlexGroup--directionColumn euiFlexGroup--responsive"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="euiButton euiButton--primary"
|
||||||
|
data-test-subj="createButton"
|
||||||
|
href="opendistro-anomaly-detection-kibana#/detectors/tempId/features"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="euiButtonContent euiButton__content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="euiButton__text"
|
||||||
|
>
|
||||||
|
Add Feature
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* A copy of the License is located at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* or in the "license" file accompanying this file. This file is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||||
|
* express or implied. See the License for the specific language governing
|
||||||
|
* permissions and limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Chart, Axis, LineSeries, RectAnnotation, niceTimeFormatter } from '@elastic/charts';
|
||||||
|
import { EuiPagination, EuiText, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||||
|
import DelayedLoader from '../../../../../components/DelayedLoader';
|
||||||
|
import { ChartContainer } from '../../../../../components/ChartContainer/ChartContainer';
|
||||||
|
|
||||||
|
class FeatureChart extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
activePage: 0,
|
||||||
|
};
|
||||||
|
this.goToPage = this.goToPage.bind(this);
|
||||||
|
}
|
||||||
|
goToPage(pageNumber) {
|
||||||
|
this.setState({
|
||||||
|
activePage: pageNumber,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const { isLoading, startDateTime, endDateTime, featureData, annotations, title } = this.props;
|
||||||
|
const currentFeature = featureData[this.state.activePage];
|
||||||
|
const timeFormatter = niceTimeFormatter([startDateTime, endDateTime]);
|
||||||
|
return (
|
||||||
|
<DelayedLoader isLoading={isLoading}>
|
||||||
|
{showLoader => {
|
||||||
|
return currentFeature ? (
|
||||||
|
<React.Fragment>
|
||||||
|
<EuiFlexGroup justifyContent="spaceBetween">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiText size="xs">
|
||||||
|
<strong>{title}</strong>
|
||||||
|
</EuiText>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiPagination
|
||||||
|
pageCount={featureData.length}
|
||||||
|
activePage={this.state.activePage}
|
||||||
|
onPageClick={this.goToPage}
|
||||||
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
<ChartContainer style={showLoader ? { opacity: 0.2 } : {}}>
|
||||||
|
<Chart>
|
||||||
|
<RectAnnotation
|
||||||
|
dataValues={annotations || []}
|
||||||
|
annotationId="react"
|
||||||
|
style={{
|
||||||
|
stroke: '#FCAAAA',
|
||||||
|
strokeWidth: 1.5,
|
||||||
|
fill: '#FCAAAA',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Axis id="left" title={currentFeature.featureName} position="left" />
|
||||||
|
<Axis id="bottom" position="bottom" tickFormat={timeFormatter} />
|
||||||
|
<LineSeries
|
||||||
|
id="lines"
|
||||||
|
xScaleType="time"
|
||||||
|
yScaleType="linear"
|
||||||
|
xAccessor={'startTime'}
|
||||||
|
yAccessors={['data']}
|
||||||
|
data={currentFeature.data}
|
||||||
|
/>
|
||||||
|
</Chart>
|
||||||
|
</ChartContainer>
|
||||||
|
</React.Fragment>
|
||||||
|
) : null;
|
||||||
|
}}
|
||||||
|
</DelayedLoader>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FeatureChart.propTypes = {
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
startDateTime: PropTypes.number.isRequired,
|
||||||
|
endDateTime: PropTypes.number.isRequired,
|
||||||
|
featureData: PropTypes.array.isRequired,
|
||||||
|
annotations: PropTypes.array,
|
||||||
|
title: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
FeatureChart.defaultProps = {
|
||||||
|
title: 'Sample feature data',
|
||||||
|
};
|
||||||
|
|
||||||
|
export { FeatureChart };
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue