unleash-server
Version:
Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.
8 lines (7 loc) • 17.4 kB
JavaScript
import{G as H,H as W,j as t,A as Y,l as J,C as I,bh as le,m as ce,o as de,iL as ue,n as pe,s as D,b as K,T as E,u as he,ba as me,r as v,b9 as Q,bb as X,bd as ge,as as Z,c7 as fe,cK as ve,iM as be,d5 as xe,d9 as M,b5 as V,iN as Se,d8 as Te,d7 as je,iO as ye,cw as Ee,bE as ee,bt as te,bc as se,iP as Ce,iQ as Re,iR as Ae,iS as we,hW as ke,hM as ae,iT as De,i as Ie,iU as Pe,c3 as ne,aQ as U,bB as Oe,fU as Fe,e as $,bF as _e,bD as Ve,bK as $e,c8 as qe,c6 as Ue,iV as Be,h as _,cm as Ne,aU as Le,aV as re,aY as B,V as Me,da as ze,aR as Ge,bi as z,bf as oe,iW as He,iX as We,B as Ye,aq as Je,hK as Ke,iY as Qe,cQ as Xe,iZ as Ze,fS as et}from"./index-BAMIkcom.js";var N={},tt=W;Object.defineProperty(N,"__esModule",{value:!0});var ie=N.default=void 0,st=tt(H()),at=t,nt=(0,st.default)((0,at.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");ie=N.default=nt;const rt=ue(),Vt=({options:e,selectedOptions:r,indeterminateOptions:a,tagType:d,existingTags:o,disabled:l=!1,onChange:s})=>{const i=t.jsx(pe,{fontSize:"small"}),u=p=>p.inputValue?p.inputValue:p.title,g=({key:p,...h},n,{selected:m})=>{const S=(a==null?void 0:a.some(x=>x.title===n.title))??!1;return t.jsxs("li",{...h,children:[t.jsx(I,{condition:!!n.inputValue,show:t.jsx(le,{sx:{mr:x=>x.spacing(.5)}}),elseShow:t.jsx(ce,{icon:i,checkedIcon:t.jsx(de,{fontSize:"small"}),indeterminateIcon:t.jsx(ie,{fontSize:"small"}),sx:{mr:x=>x.spacing(.5)},checked:m&&!S,indeterminate:S})}),n.title]},p)},f=(p,h)=>{const n=h.inputValue.trim(),m=rt(p,{...h,inputValue:n}),S=p.some(x=>n===x.title);return n.length>=2&&!S&&m.push({inputValue:n,title:`Create new value "${n}"`}),m};return t.jsx(Y,{multiple:!0,id:"checkboxes-tag",sx:{marginTop:p=>p.spacing(2),width:500},disableCloseOnSelect:!0,options:e,value:r,isOptionEqualToValue:(p,h)=>h.inputValue&&h.inputValue!==""?p.title===h.inputValue:p.title===h.title,getOptionLabel:u,renderOption:g,filterOptions:f,ListboxProps:{style:{maxHeight:200,overflow:"auto"}},onChange:s,renderInput:p=>t.jsx(J,{...p,label:"Select values",placeholder:"Select values"}),disabled:l})},ot=D("li")({flexDirection:"column"}),$t=({options:e,value:r,disabled:a=!1,onChange:d})=>{const o=K();return t.jsx(Y,{disablePortal:!0,disabled:a,id:"tag-type-select",sx:{marginTop:l=>l.spacing(2),width:500},options:e,disableClearable:!0,value:r,getOptionLabel:l=>l.name,renderOption:({key:l,...s},i)=>t.jsxs(ot,{...s,style:{alignItems:"flex-start",gap:o.spacing(.5)},children:[t.jsx(E,{variant:"body1",children:i.name}),t.jsx(E,{variant:"caption",children:i.description})]},l),renderInput:l=>t.jsx(J,{...l,label:"Tag type",value:r}),onChange:d,ListboxProps:{style:{maxHeight:200,overflow:"auto"}}})},qt=()=>{const{makeRequest:e,createRequest:r,errors:a,loading:d}=he({propagateErrors:!0});return{createTag:async s=>{const u=r("api/admin/tags",{method:"POST",body:JSON.stringify(s)});return e(u.caller,u.id)},bulkUpdateTags:async(s,i)=>{const u=`api/admin/projects/${i}/tags`,g=r(u,{method:"PUT",body:JSON.stringify(s)});return e(g.caller,g.id)},errors:a,loading:d}},Ut=(e,r={})=>{const a=async()=>{const g=Q(`api/admin/tags/${e}`);return(await fetch(g,{method:"GET"}).then(X("Tags"))).json()},d=`api/admin/tags/${e}`,{data:o,error:l}=me(!!e,{tags:[]},d,a,r),[s,i]=v.useState(!l&&!o),u=()=>{ge(d)};return v.useEffect(()=>{i(!l&&!o)},[o,l]),{tags:(o==null?void 0:o.tags)||[],error:l,loading:s,refetch:u}},it=D("div")({display:"flex",flexDirection:"column"}),lt=({strategy:e,setStrategy:r,projectId:a,environment:d,editable:o,permission:l=Te})=>{const{trackEvent:s}=Z(),[i,u]=v.useState([]),g=K(),f=e!=null&&e.parameters&&"stickiness"in(e==null?void 0:e.parameters)?String(e.parameters.stickiness):"default";v.useEffect(()=>{u((e.variants||[]).map(n=>({...n,new:o||!1,isValid:!0,id:crypto.randomUUID(),overrides:[]})))},[]),v.useEffect(()=>{r(n=>({...n,variants:i.map(m=>({stickiness:f,name:m.name,weight:m.weight,payload:m.payload,weightType:m.weightType}))}))},[f,JSON.stringify(i)]);const p=(n,m)=>{u(S=>M(S.map(x=>x.id===m?n:x),1e3))},h=()=>{const n=crypto.randomUUID();u(m=>[...m,{name:"",weightType:je.VARIABLE,weight:0,stickiness:f,new:!0,isValid:!1,id:n}]),s("strategy-variants",{props:{eventType:"variant added"}})};return t.jsxs(t.Fragment,{children:[t.jsxs(E,{component:"h3",sx:{m:0,display:"flex",gap:"1ch"},variant:"h3",children:["Variants",t.jsx(fe,{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(ve,{target:"_blank",href:"https://docs.getunleash.io/concepts/strategy-variants",children:"Learn more"})]})})]}),t.jsxs(it,{children:[t.jsx(be,{}),i.map((n,m)=>t.jsx(xe,{disableOverrides:!0,variant:n,variants:i,updateVariant:S=>p(S,n.id),removeVariant:()=>u(S=>M(S.filter(x=>x.id!==n.id),1e3)),decorationColor:g.palette.variants[m%g.palette.variants.length]},n.id))]}),t.jsx(V,{onClick:h,variant:"outlined",permission:l,projectId:a,environmentId:d,"data-testid":"ADD_STRATEGY_VARIANT_BUTTON",children:"Add variant"}),t.jsx(Se,{variants:i})]})},ct=D("form")(({theme:e})=>({display:"grid",gap:e.spacing(2)})),G=D("hr")(({theme:e})=>({width:"100%",height:"1px",margin:e.spacing(2,0),border:"none",background:e.palette.background.elevation2})),dt=D("div")(({theme:e})=>({display:"flex",justifyContent:"end",gap:e.spacing(2),paddingBottom:e.spacing(10)})),ut=({projectId:e,environmentId:r,permission:a,onSubmit:d,onCancel:o,loading:l,strategy:s,setStrategy:i,segments:u,setSegments:g,errors:f})=>{const p=ye(s.constraints),h=Ee(a,e,r),{strategyDefinition:n}=ee(s==null?void 0:s.name),m=te(),{uiConfig:S,error:x,loading:P}=se();if(x)throw x;if(P||!n)return null;const O=T=>n.parameters.find(y=>y.name===T),C=(T,y)=>{const R=O(T);if(T!=="groupId"){const k=Pe(R,y);return k?(f.setFormError(T,k),!1):(f.removeFormError(T),!0)}return!0},c=()=>n.parameters.map(T=>T.name).map(T=>{var y;return C(T,(y=s.parameters)==null?void 0:y[T])}).every(Boolean),w=()=>{m(`/projects/${e}/settings/default-strategy`)},F=async T=>{if(T.preventDefault(),c())d();else return};return t.jsxs(ct,{onSubmit:F,children:[t.jsx(Ce,{title:s.title||"",setTitle:T=>{i(y=>({...y,title:T}))}}),t.jsx(Re,{segments:u,setSegments:g,projectId:e}),t.jsx(Ae,{projectId:e,environmentId:r,strategy:s,setStrategy:i}),t.jsx(G,{}),t.jsx(we,{strategy:s,strategyDefinition:n,setStrategy:i,validateParameter:C,errors:f,hasAccess:h}),t.jsx(I,{condition:s.parameters!=null&&"stickiness"in s.parameters,show:t.jsx(lt,{strategy:s,setStrategy:i,environment:r,projectId:e,permission:[ke,ae]})}),t.jsx(G,{}),t.jsxs(dt,{children:[t.jsx(V,{permission:a,projectId:e,environmentId:r,variant:"contained",color:"primary",type:"submit",disabled:l||!p||f.hasFormErrors(),"data-testid":De,children:"Save strategy"}),t.jsx(Ie,{type:"button",color:"primary",onClick:o||w,disabled:l,children:"Cancel"})]})]})},pt=(e,r)=>{var s,i;const{project:a,refetch:d}=ne(e),o={name:"flexibleRollout",constraints:[],parameters:{rollout:"100",stickiness:a.defaultStickiness||"default",groupId:""}},l=(i=(s=a.environments)==null?void 0:s.find(u=>u.environment===r))==null?void 0:i.defaultStrategy;return{defaultStrategyFallback:o,strategy:l,refetch:d}},Bt=()=>{const e=U("projectId"),r=Oe("environmentId"),{refetch:a}=ne(e),{defaultStrategyFallback:d,strategy:o,refetch:l}=pt(e,r),[s,i]=v.useState(o||d),[u,g]=v.useState([]),{updateDefaultStrategy:f,loading:p}=Fe(),{strategyDefinition:h}=ee(s==null?void 0:s.name),{setToastData:n,setToastApiError:m}=$(),S=_e(),{uiConfig:x}=se(),{unleashUrl:P}=x,O=te(),{trackEvent:C}=Z(),{segments:c,refetchSegments:w}=Ve();v.useEffect(()=>{if(c&&(o!=null&&o.segments)){const R=[];for(const k of o==null?void 0:o.segments)R.push(...c.filter(j=>j.id===k));g(R)}},[JSON.stringify(c),JSON.stringify(o==null?void 0:o.segments)]);const F=ht(s,u),T=async R=>{await f(e,r,R),C("default_strategy",{props:{action:"edit",hasTitle:!!R.title}}),w(),a(),n({text:"Default Strategy updated",type:"success"})},y=async()=>{const R=`/projects/${e}/settings/default-strategy`;try{await T(F),await l(),O(R)}catch(k){m(_(k))}};return!h||!s?null:t.jsx($e,{modal:!0,title:qe((s==null?void 0:s.name)??""),description:gt,documentationLink:ft,documentationLinkLabel:vt,formatApiCode:()=>mt(e,r,F,h,P),children:t.jsx(ut,{projectId:e,strategy:s,setStrategy:i,segments:u,setSegments:g,environmentId:r,onSubmit:y,loading:p,permission:[Ue,ae],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(a=>a.id),disabled:e.disabled??!1}),mt=(e,r,a,d,o)=>{if(!o)return"";const l={...a,parameters:Be(a.parameters??{},d)},s=`${o}/api/admin/projects/${e}/environments/${r}/default-strategy}`,i=JSON.stringify(l,void 0,2);return`curl --location --request PUT '${s}' \\
--header 'Authorization: INSERT_API_KEY' \\
--header 'Content-Type: application/json' \\
--data-raw '${i}'`},gt=`
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.
`,ft="https://docs.getunleash.io/concepts/projects#project-default-strategy",vt="Default strategy documentation",Nt=e=>{const{data:r,error:a,mutate:d}=Ne(Q(`api/admin/environments/project/${e}`),bt),o=v.useMemo(()=>r||[],[r]),l=v.useCallback(async()=>{await d()},[d]);return{environments:o,refetchEnvironments:l,loading:!a&&!r,error:a}},bt=async e=>(await fetch(e).then(X("Environments")).then(a=>a.json())).environments.sort((a,d)=>a.sortOrder-d.sortOrder),xt=e=>{const{setToastData:r,setToastApiError:a}=$(),{addChange:d}=Le(),{refetch:o}=re(e),[l,s]=v.useState(!1),[i,u]=v.useState({isOpen:!1}),g=v.useCallback((h,n,m,S)=>{u({featureName:h,environment:n,enabled:m,shouldActivateDisabledStrategies:S,isOpen:!0})},[]),f=v.useCallback(()=>{u(h=>({...h,isOpen:!1}))},[]),p=v.useCallback(async()=>{try{s(!0),await d(e,i.environment,{feature:i.featureName,action:"updateEnabled",payload:{enabled:!!i.enabled,shouldActivateDisabledStrategies:!!i.shouldActivateDisabledStrategies}}),o(),u(h=>({...h,isOpen:!1})),r({type:"success",text:"Changes added to draft"})}catch(h){a(_(h)),u(n=>({...n,isOpen:!1}))}finally{s(!1)}},[d]);return{pending:l,onChangeRequestToggle:g,onChangeRequestToggleClose:f,onChangeRequestToggleConfirm:p,changeRequestDialogDetails:i}},St=({enabled:e,featureName:r,environment:a})=>t.jsxs(E,{"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:a})]}),Tt=({isOpen:e,disabled:r=!1,onConfirm:a,onClose:d,showBanner:o,environment:l,messageComponent:s})=>{const i=U("projectId"),{data:u}=re(i),{changeRequestInReviewOrApproved:g,alert:f}=ze(u),p=g(l||""),h=p?"Add to existing change request":"Add suggestion to draft";return t.jsxs(B,{open:e,primaryButtonText:h,secondaryButtonText:"Cancel",disabledPrimaryButton:r,onClick:a,onClose:d,title:"Request changes",fullWidth:!0,children:[t.jsx(I,{condition:p,show:f}),t.jsx(I,{condition:!!o,show:t.jsxs(Me,{severity:"info",sx:{mb:2},children:["Change requests feature is enabled for ",l,". 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(E,{variant:"body2",color:"text.secondary",children:"Your suggestion:"}),s]})},jt=D("ul")(({theme:e})=>({margin:e.spacing(1),paddingLeft:e.spacing(2)})),yt=({isOpen:e,onAddDefaultStrategy:r,onActivateDisabledStrategies:a,onClose:d,environment:o,featureId:l})=>{var f,p,h;const s=U("projectId"),{feature:i}=Ge(s,l),u=(h=(p=(f=i.environments)==null?void 0:f.find(({name:n})=>n===o))==null?void 0:p.strategies)==null?void 0:h.filter(({disabled:n})=>n).length,g=u?u===1?"1 disabled strategy":`${u} disabled strategies`:"disabled strategies";return t.jsxs(B,{open:e,secondaryButtonText:"Cancel",permissionButton:t.jsxs(t.Fragment,{children:[t.jsx(V,{type:"button",variant:"outlined",permission:z,projectId:s,environmentId:o,onClick:r,children:"Add default strategy"}),t.jsx(V,{type:"button",variant:"outlined",permission:z,projectId:s,environmentId:o,onClick:a,children:"Enable all strategies"})]}),onClose:d,title:`Enable feature flag in ${o}`,fullWidth:!0,children:[t.jsx(E,{sx:{mb:n=>n.spacing(3)},children:"A feature flag cannot be enabled without an enabled strategy."}),t.jsx(E,{children:"To enable this feature flag you can choose to:"}),t.jsxs(jt,{children:[t.jsx("li",{children:t.jsx(E,{children:t.jsx("strong",{children:"Add the default strategy"})})}),t.jsx("li",{children:t.jsxs(E,{children:[t.jsx("strong",{children:"Enable all the disabled strategies"})," ","(this feature flag has ",g,")"]})})]})]})},Et=e=>{const r=a=>{a<e.length&&e[a](()=>r(a+1))};r(0)},Lt=e=>{const{toggleFeatureEnvironmentOn:r,toggleFeatureEnvironmentOff:a}=oe(),{setToastData:d,setToastApiError:o}=$(),[l,s]=v.useState({open:!1,label:"",loading:!1,onClose:()=>{},onClick:()=>{}}),[i,u]=v.useState({isOpen:!1,environment:"",featureId:"",onClose:()=>{},onActivateDisabledStrategies:()=>{},onAddDefaultStrategy:()=>{}}),{pending:g,onChangeRequestToggle:f,onChangeRequestToggleClose:p,onChangeRequestToggleConfirm:h,changeRequestDialogDetails:n}=xt(e),[m,S]=v.useState(),x=v.useCallback(async(C,c)=>{let w=!1;return Et([j=>{if(c.isChangeRequestEnabled||!We(c.environmentType||""))return j();s({open:!0,label:`${C?"Enable":"Disable"} Environment`,loading:!1,onClose:()=>{var b;s(A=>({...A,open:!1})),(b=c.onRollback)==null||b.call(c)},onClick:()=>{s(b=>({...b,open:!1,loading:!0})),j()}})},j=>{if(C===!1||!c.hasStrategies||c.hasEnabledStrategies||c.hasReleasePlans)return j();u({isOpen:!0,environment:c.environmentName,featureId:c.featureId,onClose:()=>{var b;u(A=>({...A,isOpen:!1})),(b=c.onRollback)==null||b.call(c)},onActivateDisabledStrategies:()=>{u(b=>({...b,isOpen:!1})),w=!0,j()},onAddDefaultStrategy:()=>{u(b=>({...b,isOpen:!1})),j()}})},j=>{if(!c.isChangeRequestEnabled)return j();S(()=>{var b;S(void 0),(b=c.onRollback)==null||b.call(c)}),f(c.featureId,c.environmentName,C,w)},async j=>{var b,A;if(C!==!1)return j();try{await a(c.projectId,c.featureId,c.environmentName),d({type:"success",text:`Disabled in ${c.environmentName}`}),(b=c.onSuccess)==null||b.call(c)}catch(q){o(_(q)),(A=c.onRollback)==null||A.call(c)}},async j=>{var b,A;if(C!==!0)return j();try{await r(c.projectId,c.featureId,c.environmentName,w),d({type:"success",text:`Enabled in ${c.environmentName}`}),(b=c.onSuccess)==null||b.call(c)}catch(q){o(_(q)),(A=c.onRollback)==null||A.call(c)}}])},[s]),P=i.featureId.length!==0,O=t.jsxs(t.Fragment,{children:[t.jsx(He,{...l}),t.jsx(I,{condition:P,show:t.jsx(yt,{...i})}),t.jsx(Tt,{isOpen:n.isOpen,onClose:()=>{m==null||m(),p()},environment:n==null?void 0:n.environment,disabled:g,onConfirm:()=>{m==null||m(),h()},messageComponent:t.jsx(St,{enabled:n==null?void 0:n.enabled,featureName:n==null?void 0:n.featureName,environment:n.environment})})]});return{onToggle:x,modals:O}},Ct=e=>{const[r,a]=v.useState(e),d=v.useCallback(()=>a(e),[e]);return v.useEffect(()=>{a(e)},[e]),[r,a,d]},Rt=D(Ye)(()=>({mx:"auto",...Je})),Mt=({projectId:e,featureId:r,environmentName:a,value:d,onToggle:o})=>{const[l,s,i]=Ct(d),u=()=>{s(!l),requestAnimationFrame(()=>{o(!l,i)})},g=`${r}-${a}`;return t.jsx(t.Fragment,{children:t.jsx(Rt,{"data-testid":`TOGGLE-${g}`,children:t.jsx(Ke,{tooltip:l?`Disable flag in ${a}`:`Enable flag in ${a}`,checked:d,environmentId:a,projectId:e,permission:Qe,inputProps:{"aria-label":a},onClick:u,"data-testid":"permission-switch",disabled:d!==l})},g)})};var L={},At=W;Object.defineProperty(L,"__esModule",{value:!0});var wt=L.default=void 0,kt=At(H()),Dt=t,It=(0,kt.default)((0,Dt.jsx)("path",{d:"M16 11h-1V3c0-1.1-.9-2-2-2h-2c-1.1 0-2 .9-2 2v8H8c-2.76 0-5 2.24-5 5v7h18v-7c0-2.76-2.24-5-5-5zm3 10h-2v-3c0-.55-.45-1-1-1s-1 .45-1 1v3h-2v-3c0-.55-.45-1-1-1s-1 .45-1 1v3H9v-3c0-.55-.45-1-1-1s-1 .45-1 1v3H5v-5c0-1.65 1.35-3 3-3h8c1.65 0 3 1.35 3 3v5z"}),"CleaningServices");wt=L.default=It;const Pt="flag-reminders:v1",Ot=50,Ft=7,zt=({days:e=Ft,maxReminders:r=Ot}={})=>{const[a,d]=Xe(Pt,{});return{shouldShowReminder:s=>{const i=a[s];return!i||Ze(new Date,new Date(i))},snoozeReminder:(s,i=e)=>{const u=et(new Date,i).getTime();d(g=>{const f={...g,[s]:u},p=Object.entries(f);if(p.length>r){p.sort((n,m)=>n[1]-m[1]);const h=p.slice(p.length-r);return Object.fromEntries(h)}return f})}}},Gt=({isStale:e,isOpen:r,projectId:a,featureId:d,onClose:o})=>{const{setToastData:l,setToastApiError:s}=$(),{patchFeatureToggle:i}=oe(),u=t.jsx(E,{children:"Setting a flag to stale marks it for cleanup"}),g=t.jsx(E,{children:"Setting a flag to active marks it as in active use"}),f=e?"active":"stale",p=async h=>{h.stopPropagation();try{await i(a,d,[{op:"replace",path:"/stale",value:!e}]),o()}catch(n){s(_(n))}l(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(B,{open:r,secondaryButtonText:"Cancel",primaryButtonText:`Flip to ${f}`,title:`Set feature state to ${f}`,onClick:p,onClose:o,children:t.jsx(I,{condition:e,show:g,elseShow:u})})};export{Tt as C,Bt as E,Mt as F,$t as T,Ut as a,Vt as b,pt as c,Nt as d,Lt as e,zt as f,wt as g,Gt as h,qt as u};