UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

8 lines (7 loc) • 18.2 kB
import{G as re,H as oe,j as t,A as M,l as W,C as P,bh as ie,m as le,o as ce,ix as de,n as ue,s as j,b as J,T as C,u as pe,ba as ge,r as b,b9 as he,bb as me,bd as fe,as as Y,d2 as U,eB as xe,c0 as be,iy as ye,d0 as Se,d5 as z,b5 as B,iz as ve,d4 as Te,d3 as je,iA as Ee,cr as Ce,bE as K,bt as Q,bc as X,iB as Ae,iC as we,iD as Re,iE as ke,hL as Ie,hB as Z,iF as Pe,i as De,iG as Fe,dp as ee,aQ as q,bB as Oe,fL as Ve,e as _,bF as Be,bD as _e,bK as $e,b$ as qe,hO as Le,iH as Ne,h as V,eR as Ue,b8 as ze,dh as Ge,bp as He,aU as Me,aV as te,aY as L,V as We,d6 as Je,aR as Ye,bi as G,bf as ae,iI as Ke,iJ as Qe,B as Xe,aq as Ze,hz as et,iK as tt}from"./index-CBxzHo9v.js";var N={},at=oe;Object.defineProperty(N,"__esModule",{value:!0});var se=N.default=void 0,st=at(re()),nt=t,rt=(0,st.default)((0,nt.jsx)("path",{d:"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10H7v-2h10v2z"}),"IndeterminateCheckBox");se=N.default=rt;const ot=de(),Bt=({options:e,selectedOptions:r,indeterminateOptions:s,tagType:u,existingTags:o,disabled:i=!1,onChange:a})=>{const l=t.jsx(ue,{fontSize:"small"}),d=p=>p.inputValue?p.inputValue:p.title,m=({key:p,...g},n,{selected:h})=>{const S=(s==null?void 0:s.some(y=>y.title===n.title))??!1;return t.jsxs("li",{...g,children:[t.jsx(P,{condition:!!n.inputValue,show:t.jsx(ie,{sx:{mr:y=>y.spacing(.5)}}),elseShow:t.jsx(le,{icon:l,checkedIcon:t.jsx(ce,{fontSize:"small"}),indeterminateIcon:t.jsx(se,{fontSize:"small"}),sx:{mr:y=>y.spacing(.5)},checked:h&&!S,indeterminate:S})}),n.title]},p)},f=(p,g)=>{const n=g.inputValue.trim(),h=ot(p,{...g,inputValue:n}),S=p.some(y=>n===y.title);return n.length>=2&&!S&&h.push({inputValue:n,title:`Create new value "${n}"`}),h};return t.jsx(M,{multiple:!0,id:"checkboxes-tag",sx:{marginTop:p=>p.spacing(2),width:500},disableCloseOnSelect:!0,options:e,value:r,isOptionEqualToValue:(p,g)=>g.inputValue&&g.inputValue!==""?p.title===g.inputValue:p.title===g.title,getOptionLabel:d,renderOption:m,filterOptions:f,ListboxProps:{style:{maxHeight:200,overflow:"auto"}},onChange:a,renderInput:p=>t.jsx(W,{...p,label:"Select values",placeholder:"Select values"}),disabled:i})},it=j("li")({flexDirection:"column"}),_t=({options:e,value:r,disabled:s=!1,onChange:u})=>{const o=J();return t.jsx(M,{disablePortal:!0,disabled:s,id:"tag-type-select",sx:{marginTop:i=>i.spacing(2),width:500},options:e,disableClearable:!0,value:r,getOptionLabel:i=>i.name,renderOption:({key:i,...a},l)=>t.jsxs(it,{...a,style:{alignItems:"flex-start",gap:o.spacing(.5)},children:[t.jsx(C,{variant:"body1",children:l.name}),t.jsx(C,{variant:"caption",children:l.description})]},i),renderInput:i=>t.jsx(W,{...i,label:"Tag type",value:r}),onChange:u,ListboxProps:{style:{maxHeight:200,overflow:"auto"}}})},$t=()=>{const{makeRequest:e,createRequest:r,errors:s,loading:u}=pe({propagateErrors:!0});return{createTag:async a=>{const d=r("api/admin/tags",{method:"POST",body:JSON.stringify(a)});return e(d.caller,d.id)},bulkUpdateTags:async(a,l)=>{const d=`api/admin/projects/${l}/tags`,m=r(d,{method:"PUT",body:JSON.stringify(a)});return e(m.caller,m.id)},errors:s,loading:u}},qt=(e,r={})=>{const s=async()=>{const m=he(`api/admin/tags/${e}`);return(await fetch(m,{method:"GET"}).then(me("Tags"))).json()},u=`api/admin/tags/${e}`,{data:o,error:i}=ge(!!e,{tags:[]},u,s,r),[a,l]=b.useState(!i&&!o),d=()=>{fe(u)};return b.useEffect(()=>{l(!i&&!o)},[o,i]),{tags:(o==null?void 0:o.tags)||[],error:i,loading:a,refetch:d}},lt=j("div")({display:"flex",flexDirection:"column"}),ct=({strategy:e,setStrategy:r,projectId:s,environment:u,editable:o,permission:i=Te})=>{const{trackEvent:a}=Y(),[l,d]=b.useState([]),m=J(),f=e!=null&&e.parameters&&"stickiness"in(e==null?void 0:e.parameters)?String(e.parameters.stickiness):"default";b.useEffect(()=>{d((e.variants||[]).map(n=>({...n,new:o||!1,isValid:!0,id:U(),overrides:[]})))},[]),b.useEffect(()=>{r(n=>({...n,variants:l.map(h=>({stickiness:f,name:h.name,weight:h.weight,payload:h.payload,weightType:h.weightType}))}))},[f,JSON.stringify(l)]);const p=(n,h)=>{d(S=>z(S.map(y=>y.id===h?n:y),1e3))},g=()=>{const n=U();d(h=>[...h,{name:"",weightType:je.VARIABLE,weight:0,stickiness:f,new:!0,isValid:!1,id:n}]),a("strategy-variants",{props:{eventType:"variant added"}})};return t.jsxs(t.Fragment,{children:[t.jsxs(C,{component:"h3",sx:{m:0,display:"flex",gap:"1ch"},variant:"h3",children:["Variants",t.jsx(xe,{htmlTooltip:!0,tooltip:t.jsxs(t.Fragment,{children:[t.jsx("span",{children:"Variants allow to attach one or more values to this strategy. Variants at the strategy level override variants at the feature level."}),t.jsx(be,{target:"_blank",href:"https://docs.getunleash.io/reference/strategy-variants",children:"Learn more"})]})})]}),t.jsxs(lt,{children:[t.jsx(ye,{}),l.map((n,h)=>t.jsx(Se,{disableOverrides:!0,variant:n,variants:l,updateVariant:S=>p(S,n.id),removeVariant:()=>d(S=>z(S.filter(y=>y.id!==n.id),1e3)),decorationColor:m.palette.variants[h%m.palette.variants.length]},n.id))]}),t.jsx(B,{onClick:g,variant:"outlined",permission:i,projectId:s,environmentId:u,"data-testid":"ADD_STRATEGY_VARIANT_BUTTON",children:"Add variant"}),t.jsx(ve,{variants:l})]})},dt=j("form")(({theme:e})=>({display:"grid",gap:e.spacing(2)})),H=j("hr")(({theme:e})=>({width:"100%",height:"1px",margin:e.spacing(2,0),border:"none",background:e.palette.background.elevation2})),ut=j("div")(({theme:e})=>({display:"flex",justifyContent:"end",gap:e.spacing(2),paddingBottom:e.spacing(10)})),pt=({projectId:e,environmentId:r,permission:s,onSubmit:u,onCancel:o,loading:i,strategy:a,setStrategy:l,segments:d,setSegments:m,errors:f})=>{const p=Ee(a.constraints),g=Ce(s,e,r),{strategyDefinition:n}=K(a==null?void 0:a.name),h=Q(),{uiConfig:S,error:y,loading:D}=X();if(y)throw y;if(D||!n)return null;const F=v=>n.parameters.find(E=>E.name===v),A=(v,E)=>{const w=F(v);if(v!=="groupId"){const I=Fe(w,E);return I?(f.setFormError(v,I),!1):(f.removeFormError(v),!0)}return!0},c=()=>n.parameters.map(v=>v.name).map(v=>{var E;return A(v,(E=a.parameters)==null?void 0:E[v])}).every(Boolean),k=()=>{h(`/projects/${e}/settings/default-strategy`)},O=async v=>{if(v.preventDefault(),c())u();else return};return t.jsxs(dt,{onSubmit:O,children:[t.jsx(Ae,{title:a.title||"",setTitle:v=>{l(E=>({...E,title:v}))}}),t.jsx(we,{segments:d,setSegments:m,projectId:e}),t.jsx(Re,{projectId:e,environmentId:r,strategy:a,setStrategy:l}),t.jsx(H,{}),t.jsx(ke,{strategy:a,strategyDefinition:n,setStrategy:l,validateParameter:A,errors:f,hasAccess:g}),t.jsx(P,{condition:a.parameters!=null&&"stickiness"in a.parameters,show:t.jsx(ct,{strategy:a,setStrategy:l,environment:r,projectId:e,permission:[Ie,Z]})}),t.jsx(H,{}),t.jsxs(ut,{children:[t.jsx(B,{permission:s,projectId:e,environmentId:r,variant:"contained",color:"primary",type:"submit",disabled:i||!p||f.hasFormErrors(),"data-testid":Pe,children:"Save strategy"}),t.jsx(De,{type:"button",color:"primary",onClick:o||k,disabled:i,children:"Cancel"})]})]})},gt=(e,r)=>{var a,l;const{project:s,refetch:u}=ee(e),o={name:"flexibleRollout",constraints:[],parameters:{rollout:"100",stickiness:s.defaultStickiness||"default",groupId:""}},i=(l=(a=s.environments)==null?void 0:a.find(d=>d.environment===r))==null?void 0:l.defaultStrategy;return{defaultStrategyFallback:o,strategy:i,refetch:u}},Lt=()=>{const e=q("projectId"),r=Oe("environmentId"),{refetch:s}=ee(e),{defaultStrategyFallback:u,strategy:o,refetch:i}=gt(e,r),[a,l]=b.useState(o||u),[d,m]=b.useState([]),{updateDefaultStrategy:f,loading:p}=Ve(),{strategyDefinition:g}=K(a==null?void 0:a.name),{setToastData:n,setToastApiError:h}=_(),S=Be(),{uiConfig:y}=X(),{unleashUrl:D}=y,F=Q(),{trackEvent:A}=Y(),{segments:c,refetchSegments:k}=_e();b.useEffect(()=>{if(c&&(o!=null&&o.segments)){const w=[];for(const I of o==null?void 0:o.segments)w.push(...c.filter(T=>T.id===I));m(w)}},[JSON.stringify(c),JSON.stringify(o==null?void 0:o.segments)]);const O=ht(a,d),v=async w=>{await f(e,r,w),A("default_strategy",{props:{action:"edit",hasTitle:!!w.title}}),k(),s(),n({text:"Default Strategy updated",type:"success"})},E=async()=>{const w=`/projects/${e}/settings/default-strategy`;try{await v(O),await i(),F(w)}catch(I){h(V(I))}};return!g||!a?null:t.jsx($e,{modal:!0,title:qe((a==null?void 0:a.name)??""),description:ft,documentationLink:xt,documentationLinkLabel:bt,formatApiCode:()=>mt(e,r,O,g,D),children:t.jsx(pt,{projectId:e,strategy:a,setStrategy:l,segments:d,setSegments:m,environmentId:r,onSubmit:E,loading:p,permission:[Le,Z],errors:S,isChangeRequest:!1})})},ht=(e,r)=>({name:e.name,title:e.title,constraints:e.constraints??[],parameters:e.parameters??{},variants:e.variants??[],segments:r.map(s=>s.id),disabled:e.disabled??!1}),mt=(e,r,s,u,o)=>{if(!o)return"";const i={...s,parameters:Ne(s.parameters??{},u)},a=`${o}/api/admin/projects/${e}/environments/${r}/default-strategy}`,l=JSON.stringify(i,void 0,2);return`curl --location --request PUT '${a}' \\ --header 'Authorization: INSERT_API_KEY' \\ --header 'Content-Type: application/json' \\ --data-raw '${l}'`},ft=` An activation strategy will only run when a feature flag is enabled and provides a way to control who will get access to the feature. If any of a feature flag's activation strategies returns true, the user will get access. `,xt="https://docs.getunleash.io/reference/projects#project-default-strategy",bt="Default strategy documentation",yt=j(Ue,{shouldForwardProp:e=>e!=="expandable"})(({theme:e,expandable:r})=>({boxShadow:"none",padding:e.spacing(.5,3,.5,2),display:"flex",alignItems:"center",borderRadius:e.shape.borderRadiusLarge,pointerEvents:"auto",opacity:1,"&&&":{cursor:r?"pointer":"default"},":focus-within":{background:"none"}})),St=j("header")(({theme:e})=>({display:"flex",columnGap:e.spacing(1),paddingRight:e.spacing(1),width:"100%",color:e.palette.text.primary,alignItems:"center",minHeight:e.spacing(8)})),vt=j("hgroup")(({theme:e})=>({display:"flex",flexFlow:"row wrap",flex:1,columnGap:e.spacing(1)})),Tt=j("p")(({theme:e})=>({width:"100%",fontSize:e.fontSizes.smallerBody,color:e.palette.text.secondary})),jt=j(ze)(({theme:e})=>({fontSize:e.typography.h2.fontSize,fontWeight:e.typography.fontWeightMedium})),ne=j("p")(({theme:e})=>({fontSize:e.fontSizes.smallerBody,color:e.palette.info.contrastText,backgroundColor:e.palette.info.light,whiteSpace:"nowrap",width:"min-content",borderRadius:e.shape.borderRadiusExtraLarge,padding:e.spacing(.5,1)})),Et=j(ne)(({theme:e})=>({fontSize:e.fontSizes.smallerBody,color:e.palette.text.secondary,backgroundColor:e.palette.neutral.light})),Ct=({strategyCount:e,releasePlanCount:r})=>{if(e===0&&r===0)return t.jsx(Et,{children:"0 strategies added"});const o=`${[r>0?"Release plan":void 0,(()=>{switch(e){case 0:return;case 1:return"1 strategy";default:return`${e} strategies`}})()].filter(Boolean).join(", ")} added`;return t.jsx(ne,{children:o})},At="environment-accordion-summary",Nt=({environmentId:e,children:r,expandable:s=!0,environmentMetadata:u,...o})=>{const i=Ge();return t.jsx(yt,{...o,expandIcon:t.jsx(He,{sx:{visibility:s?"visible":"hidden"}}),id:i,"aria-controls":`environment-accordion-${i}-content`,expandable:s,tabIndex:s?0:-1,className:At,children:t.jsxs(St,{"data-loading":!0,children:[t.jsxs(vt,{children:[t.jsx(Tt,{children:"Environment"}),t.jsx(jt,{component:"h2",children:e}),u?t.jsx(Ct,{...u}):null]}),r]})})},wt=e=>{const{setToastData:r,setToastApiError:s}=_(),{addChange:u}=Me(),{refetch:o}=te(e),[i,a]=b.useState(!1),[l,d]=b.useState({isOpen:!1}),m=b.useCallback((g,n,h,S)=>{d({featureName:g,environment:n,enabled:h,shouldActivateDisabledStrategies:S,isOpen:!0})},[]),f=b.useCallback(()=>{d(g=>({...g,isOpen:!1}))},[]),p=b.useCallback(async()=>{try{a(!0),await u(e,l.environment,{feature:l.featureName,action:"updateEnabled",payload:{enabled:!!l.enabled,shouldActivateDisabledStrategies:!!l.shouldActivateDisabledStrategies}}),o(),d(g=>({...g,isOpen:!1})),r({type:"success",text:"Changes added to draft"})}catch(g){s(V(g)),d(n=>({...n,isOpen:!1}))}finally{a(!1)}},[u]);return{pending:i,onChangeRequestToggle:m,onChangeRequestToggleClose:f,onChangeRequestToggleConfirm:p,changeRequestDialogDetails:l}},Rt=({enabled:e,featureName:r,environment:s})=>t.jsxs(C,{"data-testid":"update-enabled-message",children:[t.jsx("strong",{children:e?"Enable":"Disable"})," feature flag"," ",t.jsx("strong",{children:r})," in ",t.jsx("strong",{children:s})]}),kt=({isOpen:e,disabled:r=!1,onConfirm:s,onClose:u,showBanner:o,environment:i,messageComponent:a})=>{const l=q("projectId"),{data:d}=te(l),{changeRequestInReviewOrApproved:m,alert:f}=Je(d),p=m(i||""),g=p?"Add to existing change request":"Add suggestion to draft";return t.jsxs(L,{open:e,primaryButtonText:g,secondaryButtonText:"Cancel",disabledPrimaryButton:r,onClick:s,onClose:u,title:"Request changes",fullWidth:!0,children:[t.jsx(P,{condition:p,show:f}),t.jsx(P,{condition:!!o,show:t.jsxs(We,{severity:"info",sx:{mb:2},children:["Change requests feature is enabled for ",i,". Your changes need to be approved before they will be live. All the changes you do now will be added into a draft that you can submit for review."]})}),t.jsx(C,{variant:"body2",color:"text.secondary",children:"Your suggestion:"}),a]})},It=j("ul")(({theme:e})=>({margin:e.spacing(1),paddingLeft:e.spacing(2)})),Pt=({isOpen:e,onAddDefaultStrategy:r,onActivateDisabledStrategies:s,onClose:u,environment:o,featureId:i})=>{var f,p,g;const a=q("projectId"),{feature:l}=Ye(a,i),d=(g=(p=(f=l.environments)==null?void 0:f.find(({name:n})=>n===o))==null?void 0:p.strategies)==null?void 0:g.filter(({disabled:n})=>n).length,m=d?d===1?"1 disabled strategy":`${d} disabled strategies`:"disabled strategies";return t.jsxs(L,{open:e,secondaryButtonText:"Cancel",permissionButton:t.jsxs(t.Fragment,{children:[t.jsx(B,{type:"button",variant:"outlined",permission:G,projectId:a,environmentId:o,onClick:r,children:"Add default strategy"}),t.jsx(B,{type:"button",variant:"outlined",permission:G,projectId:a,environmentId:o,onClick:s,children:"Enable all strategies"})]}),onClose:u,title:`Enable feature flag in ${o}`,fullWidth:!0,children:[t.jsx(C,{sx:{mb:n=>n.spacing(3)},children:"A feature flag cannot be enabled without an enabled strategy."}),t.jsx(C,{children:"To enable this feature flag you can choose to:"}),t.jsxs(It,{children:[t.jsx("li",{children:t.jsx(C,{children:t.jsx("strong",{children:"Add the default strategy"})})}),t.jsx("li",{children:t.jsxs(C,{children:[t.jsx("strong",{children:"Enable all the disabled strategies"})," ","(this feature flag has ",m,")"]})})]})]})},Dt=e=>{const r=s=>{s<e.length&&e[s](()=>r(s+1))};r(0)},Ut=e=>{const{toggleFeatureEnvironmentOn:r,toggleFeatureEnvironmentOff:s}=ae(),{setToastData:u,setToastApiError:o}=_(),[i,a]=b.useState({open:!1,label:"",loading:!1,onClose:()=>{},onClick:()=>{}}),[l,d]=b.useState({isOpen:!1,environment:"",featureId:"",onClose:()=>{},onActivateDisabledStrategies:()=>{},onAddDefaultStrategy:()=>{}}),{pending:m,onChangeRequestToggle:f,onChangeRequestToggleClose:p,onChangeRequestToggleConfirm:g,changeRequestDialogDetails:n}=wt(e),[h,S]=b.useState(),y=b.useCallback(async(A,c)=>{let k=!1;return Dt([T=>{if(c.isChangeRequestEnabled||!Qe(c.environmentType||""))return T();a({open:!0,label:`${A?"Enable":"Disable"} Environment`,loading:!1,onClose:()=>{var x;a(R=>({...R,open:!1})),(x=c.onRollback)==null||x.call(c)},onClick:()=>{a(x=>({...x,open:!1,loading:!0})),T()}})},T=>{if(A===!1||!c.hasStrategies||c.hasEnabledStrategies)return T();d({isOpen:!0,environment:c.environmentName,featureId:c.featureId,onClose:()=>{var x;d(R=>({...R,isOpen:!1})),(x=c.onRollback)==null||x.call(c)},onActivateDisabledStrategies:()=>{d(x=>({...x,isOpen:!1})),k=!0,T()},onAddDefaultStrategy:()=>{d(x=>({...x,isOpen:!1})),T()}})},T=>{if(!c.isChangeRequestEnabled)return T();S(()=>{var x;S(void 0),(x=c.onRollback)==null||x.call(c)}),f(c.featureId,c.environmentName,A,k)},async T=>{var x,R;if(A!==!1)return T();try{await s(c.projectId,c.featureId,c.environmentName),u({type:"success",text:`Disabled in ${c.environmentName}`}),(x=c.onSuccess)==null||x.call(c)}catch($){o(V($)),(R=c.onRollback)==null||R.call(c)}},async T=>{var x,R;if(A!==!0)return T();try{await r(c.projectId,c.featureId,c.environmentName,k),u({type:"success",text:`Enabled in ${c.environmentName}`}),(x=c.onSuccess)==null||x.call(c)}catch($){o(V($)),(R=c.onRollback)==null||R.call(c)}}])},[a]),D=l.featureId.length!==0,F=t.jsxs(t.Fragment,{children:[t.jsx(Ke,{...i}),t.jsx(P,{condition:D,show:t.jsx(Pt,{...l})}),t.jsx(kt,{isOpen:n.isOpen,onClose:()=>{h==null||h(),p()},environment:n==null?void 0:n.environment,disabled:m,onConfirm:()=>{h==null||h(),g()},messageComponent:t.jsx(Rt,{enabled:n==null?void 0:n.enabled,featureName:n==null?void 0:n.featureName,environment:n.environment})})]});return{onToggle:y,modals:F}},Ft=e=>{const[r,s]=b.useState(e),u=b.useCallback(()=>s(e),[e]);return b.useEffect(()=>{s(e)},[e]),[r,s,u]},Ot=j(Xe)(()=>({mx:"auto",...Ze})),zt=({projectId:e,featureId:r,environmentName:s,value:u,onToggle:o})=>{const[i,a,l]=Ft(u),d=()=>{a(!i),requestAnimationFrame(()=>{o(!i,l)})},m=`${r}-${s}`;return t.jsx(t.Fragment,{children:t.jsx(Ot,{"data-testid":`TOGGLE-${m}`,children:t.jsx(et,{tooltip:i?`Disable flag in ${s}`:`Enable flag in ${s}`,checked:u,environmentId:s,projectId:e,permission:tt,inputProps:{"aria-label":s},onClick:d,"data-testid":"permission-switch",disabled:u!==i})},m)})},Gt=({isStale:e,isOpen:r,projectId:s,featureId:u,onClose:o})=>{const{setToastData:i,setToastApiError:a}=_(),{patchFeatureToggle:l}=ae(),d=t.jsx(C,{children:"Setting a flag to stale marks it for cleanup"}),m=t.jsx(C,{children:"Setting a flag to active marks it as in active use"}),f=e?"active":"stale",p=async g=>{g.stopPropagation();try{await l(s,u,[{op:"replace",path:"/stale",value:!e}]),o()}catch(n){a(V(n))}i(e?{type:"success",text:"The flag is no longer marked as stale"}:{type:"success",text:"The flag has been marked as stale"})};return t.jsx(L,{open:r,secondaryButtonText:"Cancel",primaryButtonText:`Flip to ${f}`,title:`Set feature state to ${f}`,onClick:p,onClose:o,children:t.jsx(P,{condition:e,show:m,elseShow:d})})};export{kt as C,Nt as E,zt as F,_t as T,qt as a,Bt as b,gt as c,Ut as d,At as e,Gt as f,Lt as g,$t as u};