[SR] Snapshot and Restore UI#39193
Conversation
|
Pinging @elastic/es-ui |
There was a problem hiding this comment.
I really like seeing the de/serialization logic on the client again, in order to enable the JSON view of the request! I've looked through the code and left some comments. Things look great overall. I really enjoyed reading the code. I still need to test locally.
| import { serializeRestoreSettings } from './restore_serialization'; | ||
|
|
||
| describe('restore_serialization', () => { | ||
| describe('restore_serialization()', () => { |
There was a problem hiding this comment.
Nit: this second describe is so similar to the first that it might just end up being noise in the terminal.
| describe('restore_serialization()', () => { | ||
| it('should serialize restore settings', () => { | ||
| expect(serializeRestoreSettings({})).toEqual({}); | ||
| expect( |
There was a problem hiding this comment.
This assertion seems like a subset of the next assertion. Is it testing something different? If so, maybe it could use its own it description to make the difference clear, and if not then could we remove it?
| try { | ||
| parsedIndexSettings = JSON.parse(indexSettings); | ||
| } catch (e) { | ||
| // Silently swallow parsing errors |
There was a problem hiding this comment.
Would throwing the error hurt? If so, can we add a comment for the rationale behind silently swallowing the error? If not, can we throw it to be a bit more defensive and transparent?
| ignore_index_settings: ignoreIndexSettings, | ||
| }; | ||
|
|
||
| return Object.entries(settings).reduce((sts: RestoreSettingsEs, [key, value]) => { |
There was a problem hiding this comment.
I can read this just fine, but maybe a comment along the lines of // Remove undefined settings. would reduce grok time from 5 seconds to 0. Or even making this a local function called removeUndefinedSettings at the top of the file.
| shards: Array<Partial<SnapshotRecoveryShard>>; | ||
| } | ||
|
|
||
| export interface SnapshotRecoveryShard { |
There was a problem hiding this comment.
Nice types! I find these really informative (which is an unusual development in my relationship with types). 😄
| setError(response.error); | ||
| setData(response.data); | ||
| setLoading(false); | ||
| // Only set data if we are doing polling |
There was a problem hiding this comment.
Nit: it looks like data is set in both conditions, so maybe this comment could be clarified.
There was a problem hiding this comment.
EDIT: I left another comment below which may make this moot.
| setLoading(true); | ||
| const isPollRequest = currentInterval && !isFirstRequest.current; | ||
|
|
||
| // Don't reset main error/loading states if we are doing polling |
There was a problem hiding this comment.
Can we also explain the "why" from a UX perspective?
// Don't reset main error/loading states if we are doing polling because we want
// the reload to be transparent to the user (e.g. no spinner).There was a problem hiding this comment.
EDIT: I left another comment below which may make this moot.
| const [polling, setPolling] = useState<boolean>(false); | ||
| const [currentInterval, setCurrentInterval] = useState<UseRequest['interval']>(interval); | ||
| const intervalRequest = useRef<any>(null); | ||
| const isFirstRequest = useRef<boolean>(true); |
There was a problem hiding this comment.
Do you think we could return isFirstRequest.current as part of the return object, and the UI could just use that to determine whether or not to show the loading spinner? That would mean we wouldn't need the polling state or isPollRequest logic in this hook.
If that's possible then I think that'd be an improvement because then the UX would be clearer when reading the consuming code and this hook would become more reusable. I'd love to extract this hook so we can reuse it in all of our apps and I can think of situations where we may want to show a subtle spinner when polling (e.g. when refreshing Console autocomplete definitions).
There was a problem hiding this comment.
I will defer any changes to this hook for when we do re-use it outside this app. as it currently is, the recovery status table does show a subtle spinner (next to interval dropdown) when a poll request is in progress by using the current returned polling state
| return str ? !Boolean(str.trim()) : true; | ||
| }; | ||
|
|
||
| export const validateRestore = (restoreSettings: RestoreSettings): RestoreValidation => { |
There was a problem hiding this comment.
I think this file is a great candidate for some unit tests.
There was a problem hiding this comment.
will add in a follow up
| }, | ||
| { | ||
| index: 'barIndex', | ||
| shards: [{}, {}], |
There was a problem hiding this comment.
This seems strange... from the deserialization and endpoint logic, it looks like these shards should contain some of the ES data, e.g. index: { size: {}, files: {} }. Do you know why we expect them to be empty objects?
There was a problem hiding this comment.
the deserialization logic strips out undefined settings after destructuring (aka index: { size: {}, files: {} } is essentially flattened, then stripped out)
|
@jen-huang Great job! this UI is awesome |
|
@yaronp68 Thanks for looking! Both of your suggestions were things I thought about. The recovery status UI uses the Indices Recovery API, which shows recovery status keyed by index name. If a snapshot or repository that was used to recover an index is deleted, this API will still return both of their names, so if we link to snapshot or repository from this table, it's possible that it will be a 404. A future enhancement may be flagging the link so that a nicer 404 message is returned instead of the default one, like "This repository is no longer registered." There is no way to get recovery information by "recovery ID", the above API returns every index that has had any recovery operation performed, so the list of indices in this UI includes ones that have snapshot recovery in progress, and ones from the past that were recovered from a snapshot. When the user gets redirected to this tab, it's not just the indices they have just selected to recover from that snapshot because we can't retrieve the recovery information for just that recovery request, so that means other recovery information like global state isn't available. |
alisonelizabeth
left a comment
There was a problem hiding this comment.
@jen-huang This looks really awesome! Nice job!
A couple minor things I noticed while testing:
- Breadcrumb is still
Snapshot Repositories. I did a quick search in the code, and it looks like there is another reference inapp.tsxand also in one of the tests that needs to be fixed. - The
Logisticsdoc link points to the main snapshot restore docs. Maybe it should go here instead? https://www.elastic.co/guide/en/elasticsearch/reference/master//modules-snapshots.html#restore-snapshot - What do you think about clearing the values when you toggle a field back off in the restore wizard? For example, if I choose to modify index settings, then toggle it off and turn it back on again, the values I initially entered remain.
- If you go back to a previous step, should the EuiSteps reflect that?
- Should
Includes global statehave a value here?
| {hiddenIndicesCount ? ( | ||
| <li key="hiddenIndicesCount"> | ||
| <EuiTitle size="xs"> | ||
| <EuiLink onClick={() => setIsShowingFullIndicesList(true)}> |
There was a problem hiding this comment.
Should we allow the user to toggle this (hide indices again)?
| } | ||
| )} | ||
| > | ||
| <EuiText size="xs" textAlign="center" style={{ width: '100%' }}> |
There was a problem hiding this comment.
Can this inline style be added to a .scss file?
There was a problem hiding this comment.
this was actually no longer needed 😄
| setIsIntervalMenuOpen(false); | ||
| }} | ||
| > | ||
| {interval >= 60 * 1000 ? ( |
There was a problem hiding this comment.
if you follow @cjcenizal suggestion, maybe you can also update this to use the const?
| <FormattedMessage | ||
| id="xpack.snapshotRestore.recoveryList.intervalMenu.intervalValue" | ||
| defaultMessage="{minutes} {minutes, plural, one {minute} other {minutes}}" | ||
| values={{ minutes: Math.ceil(interval / (60 * 1000)) }} |
|
|
@cjcenizal @alisonelizabeth Thanks for the reviews! I've addressed or replied to all of them. There are a few UI enhancement ideas collected from design and stakeholder reviews that I'm currently working on, but as to not block this PR I'd like to submit them in a follow up PR so that this one can get merged soon. Please let me know if you have anything else you'd like me to adjust before approval 😄 |
💔 Build Failed |
💚 Build Succeeded |
alisonelizabeth
left a comment
There was a problem hiding this comment.
@jen-huang changes LGTM, thanks!
I did happen to notice this error in the console when I was registering a repository, not sure if it existed before not.
💚 Build Succeeded |
* Change app name and change default tab to snapshots * Fix i18n issues * UI placeholder for deleting snapshots * Add bulk snapshot delete endpoint and test, adjust UI delete components * Add restore action buttons * Set up restore snapshot form entry * Add RestoreSettings type * Restore step general * Combobox for ignore settings * Code editor for modifying index settings * Truncate list of indices in snapshot details and provide link to show all * Disable include global state option for snapshots that don't have global state * Add step titles, rename General to Logistics * Committing deleted file * Change repository detail settings to reverse list style * Review summary tab and placeholder json tab * Add restore wizard validation * Add restore serialization * Create restore endpoint and integration * Move new files to /legacy * Fix bugs, add search filter bar to indices list * Allow de/select all of indices * Create new recovery status tab * Prefix hook methods with `use` * Remove unnecessary RouteComponentProps * Add get all snapshot recoveries endpoint, deserialization, types, and tests * Remove unused timeout variable; enhance to allow polling (interval'd requests) without resetting state like loading * Add recovery table * Use shim'd i18n * Adjust disabled restore state * Fix invariant error * Fix partial restore label * Fix misc bugs * Address copywriting feedback * Address i18n feedback * Address PR feedback * Rename recovery to restore * Add toggle show/hide link to restore wizard summary * Fix snapshot tests due to changes in includeGlobalState deserialization format * Fix EuiCard warning



Snapshot and Restore UI
This PR adds phase 2 of Snapshot and Restore app (phase 1 PR). Previously this app was named Snapshot Repositories, now that this phase is complete, this app can finally be named properly 😄 This phase adds the following functionality:
Outstanding items
A few items are currently outstanding for this feature and will be handled in separate PRs after this one is merged:
Screenshots
I took too many screenshots so for your convenience they are grouped in collapsible sections below!
Delete snapshots
.Single delete, either by selecting one row, clicking trash can icon, or Delete button from details panel

Bulk delete by selecting multiple rows


Restore snapshot - entry
.Restore icon in row

If the snapshot is still in-progress, or otherwise invalid, restore icon is disabled

Restore button in details

Disabled restore button in details

Restore snapshot - wizard
.Step 1: Logistics
Include global state toggle is disabled when the snapshot does not have global state available

If all indices is toggled off, selectable list appears. Same pattern for rename indices toggle. All fields have real time validation

Step 2: Index settings
This step is optional

Modified index settings accepts a JSON input and checks for certain un-modifiable setting keys

Reset index settings is a combobox field that allows custom input for additional setting keys not listed in suggestions:

Step 3: Review
Executing restore loading state, after a few seconds, user is redirected to Recovery status tab

Recovery status
.Table with indices that have been recovered, or are being recovered, from a snapshot. Each row is expandable to drill down into per-shard recovery details

This table polls every 30 seconds by default, and the user can change the frequency of the polling

Every data update from a poll request will not interfere with the table UX - aka, there will be no flash of loading screen, current page will not reset, and expanded rows will stay expanded
