Skip to content

Commit f7cbdac

Browse files
authored
Merge pull request #6 from Flagsmith/chore/update-endpoints-to-limit-features
fix: Update endpoints to limit features
2 parents fa99526 + 2bf81d1 commit f7cbdac

File tree

2 files changed

+142
-94
lines changed

2 files changed

+142
-94
lines changed

flagsmith-jira-app/src/IssueFlagPanel.tsx

Lines changed: 95 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ import {
2727
EnvironmentModel,
2828
FLAGSMITH_APP,
2929
FeatureModel,
30-
FeatureStateValue,
30+
EnvironmentFeatureState,
3131
FlagModel,
3232
fetchEnvironments,
3333
fetchFeatures,
34-
fetchFlags,
34+
fetchFeatureState,
3535
} from "./flagsmith";
3636
import { canEditIssue, readFeatureIds, readProjectId, writeFeatureIds } from "./jira";
3737
import { readApiKey, readOrganisationId } from "./storage";
@@ -40,13 +40,15 @@ import { readApiKey, readOrganisationId } from "./storage";
4040
ForgeUI;
4141

4242
type IssueFlagFormProps = {
43-
features: FeatureModel[];
44-
featureIds: string[];
43+
flagsmithFeatures: FeatureModel[];
44+
jiraFeatureIds: string[];
4545
onAdd: (featureId: string) => Promise<void>;
4646
};
4747

48-
const IssueFlagForm = ({ features, featureIds, onAdd }: IssueFlagFormProps) => {
49-
const addableFeatures = features.filter((feature) => !featureIds.includes(String(feature.id)));
48+
const IssueFlagForm = ({ flagsmithFeatures, jiraFeatureIds, onAdd }: IssueFlagFormProps) => {
49+
const addableFeatures = flagsmithFeatures.filter(
50+
(feature) => !jiraFeatureIds.includes(String(feature.id)),
51+
);
5052

5153
return (
5254
<Fragment>
@@ -71,45 +73,86 @@ const IssueFlagForm = ({ features, featureIds, onAdd }: IssueFlagFormProps) => {
7173

7274
type IssueFlagTableProps = {
7375
projectUrl: string;
76+
apiKey: string;
7477
environments: EnvironmentModel[];
75-
features: FeatureModel[];
76-
flags: Record<string, FlagModel[]>;
77-
featureIds: string[];
78+
flagsmithFeatures: FeatureModel[];
79+
jiraFeatureIds: string[];
7880
onRemove: (featureId: string) => Promise<void>;
7981
canEdit: boolean;
8082
};
8183

8284
const IssueFlagTable = ({
8385
projectUrl,
86+
apiKey,
8487
environments,
85-
features,
86-
flags,
87-
featureIds,
88+
flagsmithFeatures,
89+
jiraFeatureIds,
8890
onRemove,
8991
canEdit,
9092
}: IssueFlagTableProps) => {
91-
if (featureIds.length === 0) {
93+
const [environmentFlags, setEnvironmentFlags] = useState<any>([]);
94+
95+
useEffect(async () => {
96+
// Filtered features by comparing their IDs with the feature IDs stored in Jira.
97+
const flagsmithfeaturesFiltered = flagsmithFeatures.filter((f) =>
98+
jiraFeatureIds.includes(String(f.id)),
99+
);
100+
try {
101+
if (environments.length > 0 && flagsmithfeaturesFiltered.length > 0) {
102+
const featureState: any = {};
103+
// Iterate over each filtered feature.
104+
for (const feature of flagsmithfeaturesFiltered) {
105+
// Initialize an object to store the state of the feature.
106+
featureState[String(feature.name)] = {
107+
name: feature.name,
108+
feature_id: feature.id,
109+
description: feature.description,
110+
environments: [],
111+
};
112+
for (const environment of environments) {
113+
// Obtain the feature states of the filtered features.
114+
const ffData = await fetchFeatureState({
115+
apiKey,
116+
featureName: feature.name,
117+
envAPIKey: String(environment.api_key),
118+
});
119+
ffData.name = environment.name;
120+
ffData.api_key = String(environment.api_key);
121+
// Add the feature state data to the feature state object.
122+
featureState[String(feature.name)].environments.push(ffData);
123+
}
124+
}
125+
const ffArray = Object.keys(featureState).map((featureName) => featureState[featureName]);
126+
setEnvironmentFlags(ffArray);
127+
} else {
128+
setEnvironmentFlags([]);
129+
}
130+
} catch (error) {
131+
if (!(error instanceof Error)) throw error;
132+
}
133+
}, [apiKey, jiraFeatureIds, environments, flagsmithFeatures]);
134+
135+
if (jiraFeatureIds.length === 0) {
92136
return <Text>No feature flags are linked to this issue.</Text>;
93137
}
94138

95139
let first = true;
96140
return (
97141
<Fragment>
98-
{featureIds.map((featureId) => {
99-
const feature = features.find((each) => String(each.id) === String(featureId));
142+
{environmentFlags.map((environmentFlag: FlagModel) => {
100143
// add vertical space to separate the previous feature's Remove button from this feature
101144
const spacer = first ? null : <Text>&nbsp;</Text>;
102145
first = false;
103146
return (
104-
!!feature && (
105-
<Fragment key={featureId}>
147+
!!environmentFlag && (
148+
<Fragment key={environmentFlag.feature_id}>
106149
{canEdit && spacer}
107150
<Text>
108151
<Strong>
109-
{feature.name}
110-
{feature.description ? ": " : ""}
152+
{environmentFlag.name}
153+
{environmentFlag.description ? ": " : ""}
111154
</Strong>
112-
{feature.description}
155+
{environmentFlag.description}
113156
</Text>
114157
<Table>
115158
<Head>
@@ -126,36 +169,30 @@ const IssueFlagTable = ({
126169
<Text>Last updated</Text>
127170
</Cell>
128171
</Head>
129-
{environments.map((environment) => {
130-
const environmentFlags = flags[String(environment.id)] ?? [];
131-
// get the default state for this environment
132-
const flag = environmentFlags.find(
133-
(each) =>
134-
String(each.feature) === String(featureId) &&
135-
each.feature_segment === null &&
136-
each.identity === null,
137-
);
172+
{environmentFlag?.environments.map((flag: EnvironmentFeatureState) => {
138173
if (!flag) return null;
139-
const value: Partial<FeatureStateValue> = flag.feature_state_value ?? {};
140174
// count variations/overrides
141175
const variations = flag.multivariate_feature_state_values.length;
142176
const segments = environmentFlags.filter(
143-
(each) =>
144-
String(each.feature) === String(featureId) && each.feature_segment !== null,
177+
(each: EnvironmentFeatureState) =>
178+
String(each.feature) === String(environmentFlag.feature_id) &&
179+
each.feature_segment !== null,
145180
).length;
146181
const identities = environmentFlags.filter(
147-
(each) => String(each.feature) === String(featureId) && each.identity !== null,
182+
(each: EnvironmentFeatureState) =>
183+
String(each.feature) === String(environmentFlag.feature_id) &&
184+
each.identity !== null,
148185
).length;
149186
return (
150-
<Row key={String(featureId)}>
187+
<Row key={String(`${environmentFlag.feature_id}`)}>
151188
<Cell>
152189
<Text>
153190
<Link
154-
href={`${projectUrl}/environment/${environment.api_key}/features?feature=${featureId}`}
191+
href={`${projectUrl}/environment/${flag.api_key}/features?feature=${environmentFlag.feature_id}`}
155192
appearance="link"
156193
openNewTab
157194
>
158-
{environment.name}
195+
{flag.name}
159196
</Link>
160197
</Text>
161198
{variations > 0 && (
@@ -192,15 +229,7 @@ const IssueFlagTable = ({
192229
</Text>
193230
</Cell>
194231
<Cell>
195-
{value.type === "unicode" ? (
196-
<Text>{JSON.stringify(value.string_value)}</Text>
197-
) : value.type === "int" ? (
198-
<Text>{JSON.stringify(value.integer_value)}</Text>
199-
) : value.type === "bool" ? (
200-
<Text>{JSON.stringify(!!value.boolean_value)}</Text>
201-
) : (
202-
<Text>Unknown type: {value.type}</Text>
203-
)}
232+
<Text>{flag.feature_state_value}</Text>
204233
</Cell>
205234
<Cell>
206235
<Text>
@@ -213,7 +242,10 @@ const IssueFlagTable = ({
213242
</Table>
214243
{canEdit && (
215244
<ButtonSet>
216-
<Button text="Unlink from issue" onClick={() => onRemove(featureId)} />
245+
<Button
246+
text="Unlink from issue"
247+
onClick={() => onRemove(`${environmentFlag.feature_id}`)}
248+
/>
217249
</ButtonSet>
218250
)}
219251
</Fragment>
@@ -224,15 +256,13 @@ const IssueFlagTable = ({
224256
);
225257
};
226258

227-
type Flags = Record<string, FlagModel[]>;
228-
229259
type IssueFlagPanelProps = {
230260
setError: (error: Error) => void;
231261
jiraContext: JiraContext;
232262
apiKey: string;
233263
organisationId: string;
234264
projectId: string | undefined;
235-
featureIds: string[] | undefined;
265+
jiraFeatureIds: string[] | undefined;
236266
canEdit: boolean;
237267
};
238268

@@ -245,11 +275,9 @@ const IssueFlagPanel = ({
245275
...props
246276
}: IssueFlagPanelProps) => {
247277
// set initial state
248-
const [featureIds, setFeatureIds] = useState(props.featureIds ?? []);
278+
const [jiraFeatureIds, setJiraFeatureIds] = useState(props.jiraFeatureIds ?? []);
249279
const [environments, setEnvironments] = useState([] as EnvironmentModel[]);
250280
const [features, setFeatures] = useState([] as FeatureModel[]);
251-
const [flags, setFlags] = useState({} as Flags);
252-
253281
// load environments and features
254282
useEffect(async () => {
255283
try {
@@ -263,47 +291,37 @@ const IssueFlagPanel = ({
263291
);
264292
// update form state
265293
setFeatures(features);
266-
if (environments.length > 0 && features.length > 0) {
267-
// obtain flags from API
268-
const flags: Flags = {};
269-
for (const environment of environments) {
270-
flags[String(environment.id)] = await fetchFlags({
271-
apiKey,
272-
environmentId: String(environment.id),
273-
});
274-
}
275-
// update form state
276-
setFlags(flags);
277-
}
278294
} catch (error) {
279295
if (!(error instanceof Error)) throw error;
280296
setError(error);
281297
}
282298
}, [apiKey, String(projectId)]);
283299

284-
const onChange = async (featureIds: string[]) => {
300+
const onChange = async (jiraFeatureIds: string[]) => {
285301
// persist to storage
286-
await writeFeatureIds(jiraContext, featureIds);
302+
await writeFeatureIds(jiraContext, jiraFeatureIds);
287303
// update state
288-
setFeatureIds(featureIds);
304+
setJiraFeatureIds(jiraFeatureIds);
289305
};
290-
const onAdd = (featureId: string) => onChange([...featureIds, featureId]);
306+
const onAdd = (featureId: string) => onChange([...jiraFeatureIds, featureId]);
291307
const onRemove = (featureId: string) =>
292-
onChange(featureIds.filter((each) => String(each) !== featureId));
308+
onChange(jiraFeatureIds.filter((each) => String(each) !== featureId));
293309

294310
const projectUrl = `${FLAGSMITH_APP}/project/${projectId}`;
295311
return (
296312
<Fragment>
297313
<IssueFlagTable
298314
projectUrl={projectUrl}
315+
apiKey={apiKey}
299316
environments={environments}
300-
features={features}
301-
flags={flags}
302-
featureIds={featureIds}
317+
flagsmithFeatures={features}
318+
jiraFeatureIds={jiraFeatureIds}
303319
onRemove={onRemove}
304320
canEdit={canEdit}
305321
/>
306-
{canEdit && <IssueFlagForm features={features} featureIds={featureIds} onAdd={onAdd} />}
322+
{canEdit && (
323+
<IssueFlagForm flagsmithFeatures={features} jiraFeatureIds={jiraFeatureIds} onAdd={onAdd} />
324+
)}
307325
</Fragment>
308326
);
309327
};
@@ -329,9 +347,9 @@ export default () => {
329347
const [organisationId, setOrganisationId] = useState(readOrganisationId);
330348
const jiraContext = useJiraContext();
331349
const [projectId, setProjectId] = useState(() => readProjectId(jiraContext));
332-
const [featureIds, setFeatureIds] = useState(() => readFeatureIds(jiraContext));
350+
const [jiraFeatureIds, setJiraFeatureIds] = useState(() => readFeatureIds(jiraContext));
333351
const [canEdit, setCanEdit] = useState(() => canEditIssue(jiraContext));
334-
const [editing, setEditing] = useState(!(featureIds ?? []).length);
352+
const [editing, setEditing] = useState(!(jiraFeatureIds ?? []).length);
335353

336354
const actions = canEdit
337355
? [<EditAction key="edit" editing={editing} setEditing={setEditing} />]
@@ -346,15 +364,15 @@ export default () => {
346364
jiraContext={jiraContext}
347365
organisationId={organisationId}
348366
projectId={projectId}
349-
featureIds={featureIds}
367+
jiraFeatureIds={jiraFeatureIds}
350368
canEdit={canEdit && editing}
351369
/>
352370
)}
353371
onRetry={async () => {
354372
setApiKey(await readApiKey());
355373
setOrganisationId(await readOrganisationId());
356374
setProjectId(await readProjectId(jiraContext));
357-
setFeatureIds(await readFeatureIds(jiraContext));
375+
setJiraFeatureIds(await readFeatureIds(jiraContext));
358376
setCanEdit(await canEditIssue(jiraContext));
359377
}}
360378
/>

0 commit comments

Comments
 (0)