Convert `axios` usage to `fetch` in Pace Plans

closes LS-2624
flag=pace_plans

test plan:
- with pace plans enabled, navigate to the pace plans view
- select the settings cog, and toggle the "Skip Weekends" option
- verify that the network request succeeds

Change-Id: I0fbbc4236167bbcc9d4d665b734736b88988ef1f
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/273658
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Nate Armstrong <narmstrong@instructure.com>
QA-Review: Nate Armstrong <narmstrong@instructure.com>
Product-Review: Isaac Moore <isaac.moore@instructure.com>
This commit is contained in:
Isaac Moore 2021-09-15 15:53:42 -05:00
parent 2bb8ad0d2d
commit 5acedfc484
9 changed files with 132 additions and 80 deletions

View File

@ -199,7 +199,9 @@
"@testing-library/react": "^11",
"@testing-library/react-hooks": "^5",
"@testing-library/user-event": "^12",
"@types/jquery": "^3.5.6",
"@types/lodash": "^4.14.72",
"@types/parse-link-header": "^1.0.0",
"@types/react": "^17.0.19",
"@types/react-dom": "^17.0.9",
"@typescript-eslint/eslint-plugin": "^4.29.1",

View File

@ -53,9 +53,7 @@ const updatePacePlan = async (
!persisted || shouldBlock || (plan.hard_end_dates && plan.context_type === 'Enrollment')
return method(plan, extraSaveParams) // Hit the API to update
.then(response => {
const updatedPlan: PacePlan = response.data
.then(updatedPlan => {
if (updateAfterRequest) {
dispatch(pacePlanActions.planCreated(updatedPlan))
}

View File

@ -94,8 +94,7 @@ const thunkActions = {
publishForEnrollmentIds
)
.then(response => {
const newDraft: PacePlan = response.data.new_draft_plan
dispatch(pacePlanActions.setPacePlan(newDraft))
dispatch(pacePlanActions.setPacePlan(response.new_draft_plan))
dispatch(uiActions.hideLoadingOverlay())
dispatch(uiActions.publishPlanFinished())
})
@ -120,8 +119,7 @@ const thunkActions = {
return Api.resetToLastPublished(contextType, contextId)
.then(response => {
const plan: PacePlan = response.data.pace_plan
dispatch(pacePlanActions.setPacePlan(plan))
dispatch(pacePlanActions.setPacePlan(response.pace_plan))
dispatch(uiActions.hideLoadingOverlay())
})
.catch(error => {
@ -143,8 +141,7 @@ const thunkActions = {
return Api.getLatestDraftFor(contextType, contextId)
.then(response => {
const plan: PacePlan = response.data.pace_plan
dispatch(afterAction(plan))
dispatch(afterAction(response.pace_plan))
dispatch(uiActions.hideLoadingOverlay())
})
.catch(error => {
@ -162,8 +159,7 @@ const thunkActions = {
return Api.relinkToParentPlan(getState().pacePlan.id)
.then(response => {
const plan: PacePlan = response.data.pace_plan
dispatch(pacePlanActions.setPacePlan(plan))
dispatch(pacePlanActions.setPacePlan(response.pace_plan))
dispatch(uiActions.hideLoadingOverlay())
})
.catch(error => {

View File

@ -16,22 +16,23 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import axios, {AxiosPromise} from '@canvas/axios'
import {BlackoutDate} from '../shared/types'
import * as DateHelpers from '../utils/date_stuff/date_helpers'
import doFetchApi from '@canvas/do-fetch-api-effect'
/* API methods */
export const create = (blackoutDate: BlackoutDate): AxiosPromise => {
return axios.post(`/api/v1/blackout_dates`, {
blackout_date: transformBlackoutDateForApi(blackoutDate)
})
}
export const create = async (blackoutDate: BlackoutDate) =>
(
await doFetchApi<{blackout_date: BlackoutDate}>({
path: '/api/v1/blackout_dates',
method: 'POST',
body: transformBlackoutDateForApi(blackoutDate)
})
).json
export const deleteBlackoutDate = (id: number | string): AxiosPromise => {
return axios.delete(`/api/v1/blackout_dates/${id}`)
}
export const deleteBlackoutDate = async (id: number | string) =>
(await doFetchApi({path: `/api/v1/blackout_dates/${id}`, method: 'DELETE'})).json
/* API transformers */

View File

@ -16,9 +16,8 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import axios, {AxiosPromise} from '@canvas/axios'
import {PacePlan, PlanContextTypes, WorkflowStates, PublishOptions} from '../types'
import doFetchApi from '@canvas/do-fetch-api-effect'
/* API helpers */
@ -51,66 +50,96 @@ export const waitForActionCompletion = (actionInProgress: () => boolean, waitTim
/* API methods */
export const update = (pacePlan: PacePlan, extraSaveParams = {}): AxiosPromise => {
return axios.put(`/api/v1/courses/${pacePlan.course_id}/pace_plans/${pacePlan.id}`, {
...extraSaveParams,
pace_plan: transformPacePlanForApi(pacePlan)
})
}
export const update = async (pacePlan: PacePlan, extraSaveParams = {}) =>
(
await doFetchApi<PacePlan>({
path: `/api/v1/courses/${pacePlan.course_id}/pace_plans/${pacePlan.id}`,
method: 'PUT',
body: {
...extraSaveParams,
pace_plan: transformPacePlanForApi(pacePlan)
}
})
).json
export const create = (pacePlan: PacePlan, extraSaveParams = {}): AxiosPromise => {
return axios.post(`/api/v1/courses/${pacePlan.course_id}/pace_plans`, {
...extraSaveParams,
pace_plan: transformPacePlanForApi(pacePlan)
})
}
export const create = async (pacePlan: PacePlan, extraSaveParams = {}) =>
(
await doFetchApi<PacePlan>({
path: `/api/v1/courses/${pacePlan.course_id}/pace_plans`,
method: 'POST',
body: {
...extraSaveParams,
pace_plan: transformPacePlanForApi(pacePlan)
}
})
).json
export const publish = (
export const publish = async (
plan: PacePlan,
publishForOption: PublishOptions,
publishForSectionIds: Array<string>,
publishForEnrollmentIds: Array<string>
): AxiosPromise => {
return axios.post(`/api/v1/courses/${plan.course_id}/pace_plans/publish`, {
context_type: plan.context_type,
context_id: plan.context_id,
publish_for_option: publishForOption,
publish_for_section_ids: publishForSectionIds,
publish_for_enrollment_ids: publishForEnrollmentIds
})
}
) =>
(
await doFetchApi<{new_draft_plan: PacePlan}>({
path: `/api/v1/courses/${plan.course_id}/pace_plans/publish`,
method: 'POST',
body: {
context_type: plan.context_type,
context_id: plan.context_id,
publish_for_option: publishForOption,
publish_for_section_ids: publishForSectionIds,
publish_for_enrollment_ids: publishForEnrollmentIds
}
})
).json
export const resetToLastPublished = (
contextType: PlanContextTypes,
contextId: string
): AxiosPromise => {
return axios.post(`/api/v1/pace_plans/reset_to_last_published`, {
context_type: contextType,
context_id: contextId
})
}
export const resetToLastPublished = async (contextType: PlanContextTypes, contextId: string) =>
(
await doFetchApi<{pace_plan: PacePlan}>({
path: `/api/v1/pace_plans/reset_to_last_published`,
method: 'POST',
body: {
context_type: contextType,
context_id: contextId
}
})
).json
export const load = (pacePlanId: string) => {
return axios.get(`/api/v1/pace_plans/${pacePlanId}`)
}
export const load = async (pacePlanId: string) =>
(await doFetchApi({path: `/api/v1/pace_plans/${pacePlanId}`})).json
export const getLatestDraftFor = (context: PlanContextTypes, contextId: string) => {
return axios.get(
`/api/v1/pace_plans/latest_draft_for?context_type=${context}&context_id=${contextId}`
)
}
export const getLatestDraftFor = async (context: PlanContextTypes, contextId: string) =>
(
await doFetchApi<{pace_plan: PacePlan}>({
path: `/api/v1/pace_plans/latest_draft_for?context_type=${context}&context_id=${contextId}`
})
).json
export const republishAllPlansForCourse = (courseId: string) => {
return axios.post(`/api/v1/pace_plans/republish_all_plans`, {course_id: courseId})
}
export const republishAllPlansForCourse = async (courseId: string) =>
(
await doFetchApi({
path: `/api/v1/pace_plans/republish_all_plans`,
method: 'POST',
body: {course_id: courseId}
})
).json
export const republishAllPlans = () => {
return axios.post(`/api/v1/pace_plans/republish_all_plans`)
}
export const republishAllPlans = async () =>
(
await doFetchApi({
path: `/api/v1/pace_plans/republish_all_plans`,
method: 'POST'
})
).json
export const relinkToParentPlan = (planId: string) => {
return axios.post(`/api/v1/pace_plans/${planId}/relink_to_parent_plan`)
}
export const relinkToParentPlan = async (planId: string) =>
(
await doFetchApi<{pace_plan: PacePlan}>({
path: `/api/v1/pace_plans/${planId}/relink_to_parent_plan`,
method: 'POST'
})
).json
/* API transformers
* functions and interfaces to transform the frontend formatted objects

View File

@ -57,8 +57,7 @@ const thunkActions = {
BlackoutDatesApi.create(blackoutDateWithTempId)
.then(response => {
const savedBlackoutDate: BlackoutDate = response.data.blackout_date
dispatch(actions.addBackendId(tempId, savedBlackoutDate.id as number))
dispatch(actions.addBackendId(tempId, response.blackout_date.id as number))
})
.catch(error => {
console.error(error) // eslint-disable-line no-console

View File

@ -21,22 +21,31 @@ import getCookie from 'get-cookie'
import parseLinkHeader from 'parse-link-header'
import {defaultFetchOptions} from '@instructure/js-utils'
function constructRelativeUrl({path, params}) {
function constructRelativeUrl({path, params}: {path: string; params: {[k: string]: any}}) {
const queryString = $.param(params)
if (!queryString.length) return path
return `${path}?${queryString}`
}
export type DoFetchApiOpts = {
path: string
method?: string
headers?: {[k: string]: string}
params?: {[k: string]: any}
body?: any
fetchOpts?: RequestInit
}
// NOTE: we do NOT deep-merge customFetchOptions.headers, they should be passed
// in the headers arg instead.
export default async function doFetchApi({
export default async function doFetchApi<T = any>({
path,
method = 'GET',
headers = {},
params = {},
body,
fetchOpts = {}
}) {
}: DoFetchApiOpts) {
const finalFetchOptions = {...defaultFetchOptions}
finalFetchOptions.headers['X-CSRF-Token'] = getCookie('_csrf_token')
@ -53,11 +62,12 @@ export default async function doFetchApi({
const err = new Error(
`doFetchApi received a bad response: ${response.status} ${response.statusText}`
)
err.response = response // in case anyone wants to check it for something
Object.assign(err, {response}) // in case anyone wants to check it for something
throw err
}
const link = parseLinkHeader(response.headers.get('Link'))
const linkHeader = response.headers.get('Link')
const link = linkHeader ? parseLinkHeader(linkHeader) : null
const text = await response.text()
const json = text.length > 0 ? JSON.parse(text) : null
const json = text.length > 0 ? (JSON.parse(text) as T) : null
return {json, response, link}
}

View File

@ -3,5 +3,5 @@
"private": true,
"version": "1.0.0",
"author": "neme",
"main": "./index.js"
"main": "index.ts"
}

View File

@ -6043,6 +6043,13 @@
jest-diff "^25.2.1"
pretty-format "^25.2.1"
"@types/jquery@^3.5.6":
version "3.5.6"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.6.tgz#97ac8e36dccd8ad8ed3f3f3b48933614d9fd8cf0"
integrity sha512-SmgCQRzGPId4MZQKDj9Hqc6kSXFNWZFHpELkyK8AQhf8Zr6HKfCzFv9ZC1Fv3FyQttJZOlap3qYb12h61iZAIg==
dependencies:
"@types/sizzle" "*"
"@types/js-cookie@^2.2.6":
version "2.2.7"
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3"
@ -6149,6 +6156,11 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/parse-link-header@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-link-header/-/parse-link-header-1.0.0.tgz#69f059e40a0fa93dc2e095d4142395ae6adc5d7a"
integrity sha512-fCA3btjE7QFeRLfcD0Sjg+6/CnmC66HpMBoRfRzd2raTaWMJV21CCZ0LO8MOqf8onl5n0EPfjq4zDhbyX8SVwA==
"@types/parse5@^5.0.0":
version "5.0.3"
resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109"
@ -6233,6 +6245,11 @@
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.5.2.tgz#5e2f1d120f07b9cda07e5dedd4f3bf8888fccdb9"
integrity sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==
"@types/sizzle@*":
version "2.3.3"
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef"
integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==
"@types/source-list-map@*":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"