Scaffold LTI Extensions page

This commit adds a scaffolded FE for the LTI extensions page in Canvas

test plan:

Go to Admin -> Extensions and click between Manage/Discover to make sure
the routing works and reload pages to make sure the nested urls work.

Turn the lti_registrations_discover_page feature flag off to make sure
the page the discover page is not accessible, and then turn the
lti_registrations_page off to make sure the whole extensions view is
not accessible.

fixes: INTEROP-8515
flag=lti_registrations_page

Change-Id: I7d34b387d03a4bce66325a4791645c6502784b12
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/341783
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Steve Mcgee <steve.mcgee@instructure.com>
QA-Review: Steve Mcgee <steve.mcgee@instructure.com>
Product-Review: Paul Gray <paul.gray@instructure.com>
This commit is contained in:
Paul Gray 2024-02-29 10:34:45 -05:00
parent 69b2162d67
commit ef232ca5a9
16 changed files with 346 additions and 0 deletions

View File

@ -387,6 +387,7 @@ class ApplicationController < ActionController::Base
react_discussions_post
instui_nav
enhanced_developer_keys_tables
lti_registrations_discover_page
].freeze
JS_ENV_BRAND_ACCOUNT_FEATURES = [
:embedded_release_notes

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
#
# Copyright (C) 2012 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas 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 Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
class LtiRegistrationsController < ApplicationController
def index
require_account_context
if @context.feature_enabled?(:lti_registrations_page)
set_active_tab "extensions"
add_crumb t("#crumbs.apps", "Extensions")
render :index
else
render "shared/errors/404_message", status: :not_found, formats: [:html]
end
end
end

View File

@ -1893,6 +1893,7 @@ class Account < ActiveRecord::Base
TAB_JOBS = 15
TAB_DEVELOPER_KEYS = 16
TAB_RELEASE_NOTES = 17
TAB_EXTENSIONS = 18
def external_tool_tabs(opts, user)
tools = Lti::ContextToolFinder
@ -1956,6 +1957,11 @@ class Account < ActiveRecord::Base
tabs << { id: TAB_DEVELOPER_KEYS, label: t("#account.tab_developer_keys", "Developer Keys"), css_class: "developer_keys", href: :account_developer_keys_path, account_id: root_account.id }
end
if root_account? && grants_right?(user, :manage_developer_keys) && root_account.feature_enabled?(:lti_registrations_page)
registrations_path = root_account.feature_enabled?(:lti_registrations_discover_page) ? :account_lti_registrations_path : :account_lti_manage_registrations_path
tabs << { id: TAB_EXTENSIONS, label: t("#account.tab_extensions", "Extensions"), css_class: "extensions", href: registrations_path, account_id: root_account.id }
end
tabs += external_tool_tabs(opts, user)
tabs += Lti::MessageHandler.lti_apps_tabs(self, [Lti::ResourcePlacement::ACCOUNT_NAVIGATION], opts)
Lti::ResourcePlacement.update_tabs_and_return_item_banks_tab(tabs)

View File

@ -0,0 +1,27 @@
<%
# Copyright (C) 2012 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas 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 Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
%>
<%
@show_left_side = true
%>
<% provide :page_title do %><%= t :title, "Extensions" %><% end %>
<% js_bundle :lti_registrations %>
<div id="reactContent"></div>

View File

@ -261,3 +261,22 @@ lti_placement_restrictions:
state: allowed_on
ci:
state: allowed_on
lti_registrations_page:
state: hidden
applies_to: RootAccount
display_name: LTI Extensions Page
description: |-
When enabled, the LTI Extensions page will be available in the Account navigation.
environments:
development:
state: hidden
lti_registrations_discover_page:
state: hidden
applies_to: RootAccount
display_name: LTI Extensions Discover Page
description: |-
When enabled, the LTI Extensions Discover page will be available in the Extensions view.
environments:
development:
state: hidden
custom_transition_proc: lti_registrations_discover_page_hook

View File

@ -786,6 +786,10 @@ CanvasRails::Application.routes.draw do
resources :developer_keys, only: :index
get "/developer_keys/:key_id", controller: :developer_keys, action: :index, as: "account_developer_key_view"
get "extensions", controller: :lti_registrations, action: :index, as: "lti_registrations"
get "extensions/*path", controller: :lti_registrations, action: :index
get "extensions/manage", controller: :lti_registrations, action: :index, as: "lti_manage_registrations"
get "release_notes" => "release_notes#manage", :as => :release_notes_manage
get "blackout_dates" => "blackout_dates#index"

View File

@ -143,5 +143,12 @@ module FeatureFlags
)
end
end
def self.lti_registrations_discover_page_hook(_user, context, _from_state, transitions)
unless context.feature_enabled?(:lti_registrations_page)
transitions["on"] ||= {}
transitions["on"]["message"] = I18n.t("The LTI Extensions Discover page won't be accessible unless the LTI Registrations page is enabled")
end
end
end
end

View File

@ -84,6 +84,7 @@ const featureBundles: {
dashboard: () => import('./features/dashboard/index'),
deep_linking_response: () => import('./features/deep_linking_response/index'),
developer_keys_v2: () => import('./features/developer_keys_v2/index'),
lti_registrations: () => import('./features/lti_registrations/index'),
discussion_topic_edit_v2: () => import('./features/discussion_topic_edit_v2/index'),
discussion_topic_edit: () => import('./features/discussion_topic_edit/index'),
discussion_topic: () => import('./features/discussion_topic/index'),

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas 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 Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
export const Discover = () => {
return (
<div>
<h1>This is the Discover page</h1>
</div>
)
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas 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 Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import type {RouteObject} from 'react-router-dom'
import {Discover} from './Discover'
export const DiscoverRoute: RouteObject = {
path: '/',
element: <Discover />,
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas 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 Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import ReactDOM from 'react-dom'
import {createBrowserRouter, RouterProvider, Link} from 'react-router-dom'
import {Discover} from './discover/Discover'
import {Manage} from './manage/Manage'
import {LtiAppsLayout} from './layout/LtiAppsLayout'
import {DiscoverRoute} from './discover'
import {ManageRoute} from './manage'
const getBasename = () => {
const path = window.location.pathname
const parts = path.split('/')
return parts.slice(0, parts.indexOf('extensions') + 1).join('/')
}
// window.ENV.lti_registrations_discover_page
const router = createBrowserRouter(
[
{
path: '/',
element: <LtiAppsLayout />,
children: window.ENV.FEATURES.lti_registrations_discover_page
? [DiscoverRoute, ManageRoute]
: [ManageRoute],
},
],
{
basename: getBasename(),
}
)
ReactDOM.render(<RouterProvider router={router} />, document.getElementById('reactContent'))

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas 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 Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import ReactDOM from 'react-dom'
import {createBrowserRouter, RouterProvider, Link, Outlet, useMatch} from 'react-router-dom'
import {useScope as useI18nScope} from '@canvas/i18n'
import {Heading} from '@instructure/ui-heading'
import {Spinner} from '@instructure/ui-spinner'
import {Tabs} from '@instructure/ui-tabs'
import {View} from '@instructure/ui-view'
import {Text} from '@instructure/ui-text'
const I18n = useI18nScope('lti_registrations')
export const LtiAppsLayout = () => {
const isManage = useMatch('/manage/*')
return (
<>
<View as="div" margin="0 0 small 0" padding="none">
<Heading level="h1">{I18n.t('Extensions')}</Heading>
</View>
<Text size="large">{I18n.t('Discover Something new or manage existing LTI extensions')}</Text>
<Tabs margin="medium auto" padding="medium" onRequestTabChange={() => {}}>
{window.ENV.FEATURES.lti_registrations_discover_page && (
<Tabs.Panel
isSelected={!isManage}
id="tabB"
href="/"
renderTitle={
<Link style={{color: 'initial', textDecoration: 'initial'}} to="/">
{I18n.t('Discover')}
</Link>
}
>
<Outlet />
</Tabs.Panel>
)}
<Tabs.Panel
renderTitle={
<Link style={{color: 'initial', textDecoration: 'initial'}} to="/manage">
{I18n.t('Manage')}
</Link>
}
id="tabA"
padding="large"
isSelected={!!isManage}
active={true}
>
<Outlet />
</Tabs.Panel>
</Tabs>
</>
)
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas 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 Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import {Outlet} from 'react-router-dom'
export const Manage = () => {
return (
<div>
<h1>This is the Manage page</h1>
<Outlet />
</div>
)
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas 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 Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import type {RouteObject} from 'react-router-dom'
import {Manage} from './Manage'
export const ManageRoute: RouteObject = {
path: 'manage',
element: <Manage />,
children: [
{
path: 'foo',
element: <div>Foo</div>,
},
{
path: 'bar',
element: <div>Bar</div>,
},
],
}

View File

@ -0,0 +1,6 @@
{
"name": "@canvas-features/lti_registrations",
"private": true,
"version": "1.0.0",
"owner": "INTEROP"
}

View File

@ -216,6 +216,7 @@ export type SiteAdminFeatureId =
| 'platform_service_speedgrader'
| 'render_both_to_do_lists'
| 'instui_header'
| 'lti_registrations_discover_page'
/**
* From ApplicationController#JS_ENV_ROOT_ACCOUNT_FEATURES