import {Fragment, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import capitalize from 'lodash/capitalize'; import moment from 'moment'; import {APIRequestMethod} from 'sentry/api'; import {Button} from 'sentry/components/button'; import {CompactSelect} from 'sentry/components/compactSelect'; import ProjectBadge from 'sentry/components/idBadge/projectBadge'; import Input from 'sentry/components/input'; import {IconAdd, IconClose, IconDelete, IconEdit} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {Environment, Project} from 'sentry/types'; import {getExactDuration, parseLargestSuffix} from 'sentry/utils/formatters'; import useApi from 'sentry/utils/useApi'; import {Threshold} from '../utils/types'; const NEW_THRESHOLD_PREFIX = 'newthreshold'; type Props = { columns: number; orgSlug: string; refetch: () => void; setError: (msg: string) => void; thresholds: Threshold[]; }; type EditingThreshold = { environment: Environment; id: string; project: Project; threshold_type: string; trigger_type: string; value: number; windowSuffix: moment.unitOfTime.DurationConstructor; windowValue: number; date_added?: string; hasError?: boolean; window_in_seconds?: number; }; export function ThresholdGroupRows({ thresholds, columns, orgSlug, refetch, setError, }: Props) { const [editingThresholds, setEditingThresholds] = useState<{ [key: string]: EditingThreshold; }>({}); const [newThresholdIterator, setNewThresholdIterator] = useState(0); // used simply to initialize new threshold const api = useApi(); const project = thresholds[0].project; const environment = thresholds[0].environment; const defaultWindow = thresholds[0].window_in_seconds; const thresholdsById: {[id: string]: Threshold} = useMemo(() => { const byId = {}; thresholds.forEach(threshold => { byId[threshold.id] = threshold; }); return byId; }, [thresholds]); const thresholdIdSet = useMemo(() => { return new Set([ ...thresholds.map(threshold => threshold.id), ...Object.keys(editingThresholds), ]); }, [thresholds, editingThresholds]); const initializeNewThreshold = () => { const thresholdId = `${NEW_THRESHOLD_PREFIX}-${newThresholdIterator}`; const [windowValue, windowSuffix] = parseLargestSuffix(defaultWindow); const newThreshold: EditingThreshold = { id: thresholdId, project, environment, windowValue, windowSuffix, threshold_type: 'total_error_count', trigger_type: 'over', value: 0, hasError: false, }; const updatedEditingThresholds = {...editingThresholds}; updatedEditingThresholds[thresholdId] = newThreshold; setEditingThresholds(updatedEditingThresholds); setNewThresholdIterator(newThresholdIterator + 1); }; const enableEditThreshold = thresholdId => { const updatedEditingThresholds = {...editingThresholds}; const threshold = JSON.parse(JSON.stringify(thresholdsById[thresholdId])); const [windowValue, windowSuffix] = parseLargestSuffix(threshold.window_in_seconds); updatedEditingThresholds[thresholdId] = { ...threshold, windowValue, windowSuffix, hasError: false, }; setEditingThresholds(updatedEditingThresholds); }; const saveThreshold = (thresholdIds: string[]) => { thresholdIds.forEach(id => { const thresholdData = editingThresholds[id]; const seconds = moment .duration(thresholdData.windowValue, thresholdData.windowSuffix) .as('seconds'); const submitData = { ...thresholdData, window_in_seconds: seconds, environment: thresholdData.environment.name, // api expects environment as a string }; let path = `/projects/${orgSlug}/${thresholdData.project.slug}/release-thresholds/${id}/`; let method: APIRequestMethod = 'PUT'; if (id.includes(NEW_THRESHOLD_PREFIX)) { path = `/projects/${orgSlug}/${thresholdData.project.slug}/release-thresholds/`; method = 'POST'; } const request = api.requestPromise(path, { method, data: submitData, }); request .then(() => { refetch(); closeEditForm(id); }) .catch(_err => { setError('Issue saving threshold'); setEditingThresholds(prevState => { const errorThreshold = { ...submitData, hasError: true, environment: thresholdData.environment, // convert local state environment back to object }; const updatedEditingThresholds = {...prevState}; updatedEditingThresholds[id] = errorThreshold; return updatedEditingThresholds; }); }); }); }; const deleteThreshold = thresholdId => { const updatedEditingThresholds = {...editingThresholds}; const thresholdData = editingThresholds[thresholdId]; const path = `/projects/${orgSlug}/${thresholdData.project.slug}/release-thresholds/${thresholdId}/`; const method = 'DELETE'; if (!thresholdId.includes(NEW_THRESHOLD_PREFIX)) { const request = api.requestPromise(path, { method, }); request .then(() => { refetch(); }) .catch(_err => { setError('Issue deleting threshold'); const errorThreshold = { ...thresholdData, hasError: true, }; updatedEditingThresholds[thresholdId] = errorThreshold as EditingThreshold; setEditingThresholds(updatedEditingThresholds); }); } delete updatedEditingThresholds[thresholdId]; setEditingThresholds(updatedEditingThresholds); }; const closeEditForm = thresholdId => { const updatedEditingThresholds = {...editingThresholds}; delete updatedEditingThresholds[thresholdId]; setEditingThresholds(updatedEditingThresholds); }; const editThresholdState = (thresholdId, key, value) => { if (editingThresholds[thresholdId]) { const updateEditing = JSON.parse(JSON.stringify(editingThresholds)); updateEditing[thresholdId][key] = value; setEditingThresholds(updateEditing); } }; return ( {Array.from(thresholdIdSet).map((tId: string, idx: number) => { const threshold = editingThresholds[tId] || thresholdsById[tId]; return ( {idx === 0 ? ( ) : ( '' )} {idx === 0 ? threshold.environment.name || 'None' : ''} {/* FOLLOWING COLUMNS ARE EDITABLE */} {editingThresholds[threshold.id] ? ( editThresholdState(threshold.id, 'windowValue', e.target.value) } /> editThresholdState( threshold.id, 'windowSuffix', selectedOption.value ) } options={[ { value: 'seconds', textValue: 'seconds', label: 's', }, { value: 'minutes', textValue: 'minutes', label: 'min', }, { value: 'hours', textValue: 'hours', label: 'hrs', }, { value: 'days', textValue: 'days', label: 'days', }, ]} /> editThresholdState( threshold.id, 'threshold_type', selectedOption.value ) } options={[ { value: 'total_error_count', textValue: 'Errors', label: 'Error Count', }, ]} /> {threshold.trigger_type === 'over' ? ( ) : ( )} editThresholdState(threshold.id, 'value', e.target.value) } /> ) : ( {getExactDuration(threshold.window_in_seconds || 0, false, 'seconds')}
{threshold.threshold_type .split('_') .map(word => capitalize(word)) .join(' ')}
 {threshold.trigger_type === 'over' ? '>' : '<'} 
{threshold.value}
)} {/* END OF EDITABLE COLUMNS */} {editingThresholds[threshold.id] ? ( {!threshold.id.includes(NEW_THRESHOLD_PREFIX) && (