From fc8df8815e0cf2476f0f2d3d00eebd68ec3daa17 Mon Sep 17 00:00:00 2001
From: Ahmad Farhat <ahmad.af.farhat@gmail.com>
Date: Wed, 12 Jul 2023 15:34:20 -0400
Subject: [PATCH] Various improvements throughout the app (#5325)

* Various improvements

* Invalidate right queries

* Improve recording visibility dropdown

* Fix min width

* Fix dropdown overflow

* small styling
---
 .../stylesheets/application.bootstrap.scss    | 37 +++++++++
 app/assets/stylesheets/recordings.scss        |  6 +-
 .../components/recordings/RecordingRow.jsx    | 75 ++++++++++++-------
 .../utilities/SimpleSelect.jsx                | 47 ++++++++++++
 .../useUpdateRecordingVisibility.jsx          |  2 +
 .../public_recording_serializer.rb            |  4 +
 lib/tasks/admin.rake                          | 17 +++--
 7 files changed, 153 insertions(+), 35 deletions(-)
 create mode 100644 app/javascript/components/shared_components/utilities/SimpleSelect.jsx

diff --git a/app/assets/stylesheets/application.bootstrap.scss b/app/assets/stylesheets/application.bootstrap.scss
index 746c74c8..f5fec4c7 100644
--- a/app/assets/stylesheets/application.bootstrap.scss
+++ b/app/assets/stylesheets/application.bootstrap.scss
@@ -441,6 +441,43 @@ input.search-bar {
   }
 }
 
+.simple-select {
+  button {
+    background: white !important;
+    color: black !important;
+    border-color: gainsboro !important;
+    text-align: left;
+    width: 220px;
+
+    &:hover, &:focus, &:active {
+      background: white !important;
+      color: black !important;
+      border-color: gainsboro !important;
+    }
+    &:focus {
+      box-shadow: 0 0 0 0.25rem var(--brand-color-light) !important;
+    }
+    &::after {
+      display: none;
+    }
+  }
+
+  .dropdown-menu {
+    min-width: 220px;
+  }
+}
+
+@media (max-width: 767px) {
+  .table-responsive .dropdown-menu {
+    position: static !important;
+  }
+}
+@media (min-width: 768px) {
+  .table-responsive {
+    overflow: visible;
+  }
+}
+
 input[type='text']:focus {
   border-color: whitesmoke !important;
   box-shadow: 0 0 0 2px var(--brand-color-light) !important;
diff --git a/app/assets/stylesheets/recordings.scss b/app/assets/stylesheets/recordings.scss
index f7d76ea5..9b770871 100644
--- a/app/assets/stylesheets/recordings.scss
+++ b/app/assets/stylesheets/recordings.scss
@@ -15,11 +15,13 @@
 // with Greenlight; if not, see <http://www.gnu.org/licenses/>.
 
 #user-recordings, #room-recordings {
+  min-height: 400px;
+
   table {
     border-top-right-radius: $border-radius-lg;
     border-top-left-radius: $border-radius-lg;
     border-spacing: 0;
-    overflow: hidden;
+    border-top-width: 0 !important;
   }
 
   tr {
@@ -121,4 +123,4 @@
   svg:hover {
     color: var(--brand-color) !important;
   }
-}
+}
\ No newline at end of file
diff --git a/app/javascript/components/recordings/RecordingRow.jsx b/app/javascript/components/recordings/RecordingRow.jsx
index 8fead84a..04890601 100644
--- a/app/javascript/components/recordings/RecordingRow.jsx
+++ b/app/javascript/components/recordings/RecordingRow.jsx
@@ -17,7 +17,6 @@
 import {
   VideoCameraIcon, TrashIcon, PencilSquareIcon, ClipboardDocumentIcon, EllipsisVerticalIcon,
 } from '@heroicons/react/24/outline';
-import Form from 'react-bootstrap/Form';
 import React, { useState } from 'react';
 import PropTypes from 'prop-types';
 import {
@@ -32,6 +31,7 @@ import Modal from '../shared_components/modals/Modal';
 import { localizeDateTimeString } from '../../helpers/DateTimeHelper';
 import useRedirectRecordingUrl from '../../hooks/mutations/recordings/useRedirectRecordingUrl';
 import useCopyRecordingUrl from '../../hooks/mutations/recordings/useCopyRecordingUrl';
+import SimpleSelect from '../shared_components/utilities/SimpleSelect';
 
 // TODO: Amir - Refactor this.
 export default function RecordingRow({
@@ -102,29 +102,48 @@ export default function RecordingRow({
       <td className="border-0"> {t('recording.length_in_minutes', { recording })} </td>
       <td className="border-0"> {recording.participants} </td>
       <td className="border-0">
-        {/* TODO: Refactor this. */}
-        <Form.Select
-          className="visibility-dropdown"
-          onChange={(event) => {
-            visibilityAPI.mutate({ visibility: event.target.value, id: recording.record_id });
-          }}
+        <SimpleSelect
           defaultValue={recording.visibility}
-          disabled={visibilityAPI.isLoading}
         >
-          <option value="Published">{t('recording.published')}</option>
-          <option value="Unpublished">{t('recording.unpublished')}</option>
-          {recording?.protectable === true
-            && (
-              <>
-                <option value="Protected">{t('recording.protected')}</option>
-                <option value="Public/Protected">{t('recording.public_protected')}</option>
-              </>
-            )}
-          <option value="Public">{t('recording.public')}</option>
-        </Form.Select>
+          <Dropdown.Item
+            key="Public/Protected"
+            value="Public/Protected"
+            onClick={() => visibilityAPI.mutate({ visibility: 'Public/Protected', id: recording.record_id })}
+          >
+            {t('recording.public_protected')}
+          </Dropdown.Item>
+          <Dropdown.Item
+            key="Public"
+            value="Public"
+            onClick={() => visibilityAPI.mutate({ visibility: 'Public', id: recording.record_id })}
+          >
+            {t('recording.public')}
+          </Dropdown.Item>
+          <Dropdown.Item
+            key="Protected"
+            value="Protected"
+            onClick={() => visibilityAPI.mutate({ visibility: 'Protected', id: recording.record_id })}
+          >
+            {t('recording.protected')}
+          </Dropdown.Item>
+          <Dropdown.Item
+            key="Published"
+            value="Published"
+            onClick={() => visibilityAPI.mutate({ visibility: 'Published', id: recording.record_id })}
+          >
+            {t('recording.published')}
+          </Dropdown.Item>
+          <Dropdown.Item
+            key="Unpublished"
+            value="Unpublished"
+            onClick={() => visibilityAPI.mutate({ visibility: 'Unpublished', id: recording.record_id })}
+          >
+            {t('recording.unpublished')}
+          </Dropdown.Item>
+        </SimpleSelect>
       </td>
       <td className="border-0">
-        {formats.map((format) => (
+        {recording?.visibility !== 'Unpublished' && formats.map((format) => (
           <Button
             onClick={() => redirectRecordingUrl.mutate({ record_id: recording.record_id, format: format.recording_type })}
             className={`btn-sm rounded-pill me-1 mt-1 border-0 btn-format-${format.recording_type.toLowerCase()}`}
@@ -158,13 +177,15 @@ export default function RecordingRow({
           )
           : (
             <Stack direction="horizontal" className="float-end recordings-icons">
-              <Button
-                variant="icon"
-                className="mt-1 me-3"
-                onClick={() => copyRecordingUrl.mutate({ record_id: recording.record_id })}
-              >
-                <ClipboardDocumentIcon className="hi-s text-muted" />
-              </Button>
+              { recording?.visibility !== 'Unpublished' && (
+                <Button
+                  variant="icon"
+                  className="mt-1 me-3"
+                  onClick={() => copyRecordingUrl.mutate({ record_id: recording.record_id })}
+                >
+                  <ClipboardDocumentIcon className="hi-s text-muted" />
+                </Button>
+              )}
               <Modal
                 modalButton={<Dropdown.Item className="btn btn-icon"><TrashIcon className="hi-s me-2" /></Dropdown.Item>}
                 body={(
diff --git a/app/javascript/components/shared_components/utilities/SimpleSelect.jsx b/app/javascript/components/shared_components/utilities/SimpleSelect.jsx
new file mode 100644
index 00000000..7ec44282
--- /dev/null
+++ b/app/javascript/components/shared_components/utilities/SimpleSelect.jsx
@@ -0,0 +1,47 @@
+// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
+//
+// Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
+//
+// This program is free software; you can redistribute it and/or modify it under the
+// terms of the GNU Lesser General Public License as published by the Free Software
+// Foundation; either version 3.0 of the License, or (at your option) any later
+// version.
+//
+// Greenlight is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License along
+// with Greenlight; if not, see <http://www.gnu.org/licenses/>.
+
+import { Dropdown } from 'react-bootstrap';
+import React from 'react';
+import PropTypes from 'prop-types';
+import { ChevronDownIcon } from '@heroicons/react/20/solid';
+
+export default function SimpleSelect({ defaultValue, children }) {
+  // Get the currently selected option and set the dropdown toggle to that value
+  const defaultString = children?.filter((item) => item.props.value === defaultValue)[0];
+
+  return (
+    <Dropdown className="simple-select">
+      <Dropdown.Toggle>
+        { defaultString?.props?.children }
+        <ChevronDownIcon className="hi-s float-end" />
+      </Dropdown.Toggle>
+      <Dropdown.Menu>
+        {children}
+      </Dropdown.Menu>
+    </Dropdown>
+  );
+}
+
+SimpleSelect.defaultProps = {
+  defaultValue: '',
+  children: undefined,
+};
+
+SimpleSelect.propTypes = {
+  defaultValue: PropTypes.string,
+  children: PropTypes.arrayOf(PropTypes.element),
+};
diff --git a/app/javascript/hooks/mutations/recordings/useUpdateRecordingVisibility.jsx b/app/javascript/hooks/mutations/recordings/useUpdateRecordingVisibility.jsx
index 23514102..f6488447 100644
--- a/app/javascript/hooks/mutations/recordings/useUpdateRecordingVisibility.jsx
+++ b/app/javascript/hooks/mutations/recordings/useUpdateRecordingVisibility.jsx
@@ -28,6 +28,8 @@ export default function useUpdateRecordingVisibility() {
     {
       onSuccess: () => {
         queryClient.invalidateQueries(['getRecordings']);
+        queryClient.invalidateQueries(['getRoomRecordings']);
+        queryClient.invalidateQueries(['getServerRecordings']);
         toast.success(t('toast.success.recording.recording_visibility_updated'));
       },
       onError: () => {
diff --git a/app/serializers/public_recording_serializer.rb b/app/serializers/public_recording_serializer.rb
index a687d449..bd76270f 100644
--- a/app/serializers/public_recording_serializer.rb
+++ b/app/serializers/public_recording_serializer.rb
@@ -20,4 +20,8 @@ class PublicRecordingSerializer < ApplicationSerializer
   attributes :id, :record_id, :name, :length, :recorded_at
 
   has_many :formats
+
+  def formats
+    object.formats.filter { |format| format.recording_type != 'statistics' }
+  end
 end
diff --git a/lib/tasks/admin.rake b/lib/tasks/admin.rake
index 775662d8..c9679a06 100644
--- a/lib/tasks/admin.rake
+++ b/lib/tasks/admin.rake
@@ -37,7 +37,7 @@ namespace :admin do
   task :super_admin, %i[name email password] => :environment do |_task, args|
     super_admin_email = "superadmin-#{args[:email]}"
 
-    user = User.create(
+    user = User.new(
       name: args[:name],
       email: super_admin_email,
       password: args[:password],
@@ -48,11 +48,16 @@ namespace :admin do
       language: I18n.default_locale
     )
 
-    success 'User account was created successfully!'
-    info "  Name: #{user.name}"
-    info "  Email: #{user.email}"
-    info "  Password: #{user.password}"
-    info "  Role: #{user.role.name}"
+    if user.save
+      success 'User account was created successfully!'
+      info "  Name: #{user.name}"
+      info "  Email: #{user.email}"
+      info "  Password: #{user.password}"
+      info "  Role: #{user.role.name}"
+    else
+      warning 'There was an error creating this user'
+      err "  Error: #{user.errors.full_messages}"
+    end
 
     exit 0
   end
-- 
GitLab