diff --git a/src/main/java/edu/ksu/canvas/CanvasApiFactory.java b/src/main/java/edu/ksu/canvas/CanvasApiFactory.java index 1e753be0..1639a59d 100644 --- a/src/main/java/edu/ksu/canvas/CanvasApiFactory.java +++ b/src/main/java/edu/ksu/canvas/CanvasApiFactory.java @@ -179,6 +179,7 @@ private void setupClassMap() { readerMap.put(CourseSettingsReader.class, CourseSettingsImpl.class); readerMap.put(GradingStandardReader.class, GradingStandardImpl.class); readerMap.put(ModuleReader.class, ModuleImpl.class); + readerMap.put(ModuleItemReader.class, ModuleItemImpl.class); readerMap.put(SisImportReader.class, SisImportImpl.class); readerMap.put(SelectiveDataReader.class, SelectiveDataImpl.class); readerMap.put(MigrationIssueReader.class, MigrationIssueImpl.class); diff --git a/src/main/java/edu/ksu/canvas/impl/ModuleItemImpl.java b/src/main/java/edu/ksu/canvas/impl/ModuleItemImpl.java new file mode 100644 index 00000000..4261db99 --- /dev/null +++ b/src/main/java/edu/ksu/canvas/impl/ModuleItemImpl.java @@ -0,0 +1,41 @@ +package edu.ksu.canvas.impl; + +import com.google.gson.reflect.TypeToken; +import edu.ksu.canvas.interfaces.CanvasWriter; +import edu.ksu.canvas.interfaces.ModuleItemReader; +import edu.ksu.canvas.model.ModuleItem; +import edu.ksu.canvas.net.RestClient; +import edu.ksu.canvas.oauth.OauthToken; +import edu.ksu.canvas.requestOptions.ListModuleItemsOptions; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.List; + +public class ModuleItemImpl extends BaseImpl implements ModuleItemReader { + private static final Logger LOG = LoggerFactory.getLogger(ModuleItemImpl.class); + public ModuleItemImpl(final String canvasBaseUrl, final Integer apiVersion, final OauthToken oauthToken, final RestClient restClient, final int connectTimeout, final int readTimeout, final Integer paginationPageSize, final Boolean serializeNulls) { + super(canvasBaseUrl, apiVersion, oauthToken, restClient, connectTimeout, readTimeout, paginationPageSize, serializeNulls); + } + + @Override + public List getModuleItemsInModule(final ListModuleItemsOptions options) throws IOException { + LOG.debug("Retrieving items for module {}", options.getModuleId()); + String url = buildCanvasUrl(String.format("courses/%d/modules/%d/items", options.getCourseId(), options.getModuleId()), options.getOptionsMap()); + return getListFromCanvas(url); + } + + @Override + protected Type listType() { + return new TypeToken>() { + }.getType(); + } + + @Override + protected Class objectType() { + return ModuleItem.class; + } +} \ No newline at end of file diff --git a/src/main/java/edu/ksu/canvas/interfaces/ModuleItemReader.java b/src/main/java/edu/ksu/canvas/interfaces/ModuleItemReader.java new file mode 100644 index 00000000..4976caf2 --- /dev/null +++ b/src/main/java/edu/ksu/canvas/interfaces/ModuleItemReader.java @@ -0,0 +1,18 @@ +package edu.ksu.canvas.interfaces; + +import edu.ksu.canvas.model.ModuleItem; +import edu.ksu.canvas.requestOptions.ListModuleItemsOptions; + +import java.io.IOException; +import java.util.List; + +public interface ModuleItemReader extends CanvasReader { + /** + * Retrieve the list of items in a module. + * + * @param options The object holding options for this API call + * @return List of the items in a module + * @throws IOException When there is an error communicating with Canvas + */ + public List getModuleItemsInModule(ListModuleItemsOptions options) throws IOException; +} \ No newline at end of file diff --git a/src/main/java/edu/ksu/canvas/model/ModuleItem.java b/src/main/java/edu/ksu/canvas/model/ModuleItem.java new file mode 100644 index 00000000..cf374c06 --- /dev/null +++ b/src/main/java/edu/ksu/canvas/model/ModuleItem.java @@ -0,0 +1,162 @@ +package edu.ksu.canvas.model; + +import java.io.Serializable; + +/* https://canvas.instructure.com/doc/api/modules.html#ModuleItem */ +public class ModuleItem extends BaseCanvasModel implements Serializable { + private Long id; + private Long moduleId; + private Long position; + private String title; + private Long indent; + private String type; + private Long contentId; + private String htmlUrl; + private String url; /* optional */ + private String pageUrl; /* only for 'Page' type */ + private String externalUrl; /* only for 'ExternalUrl' and 'ExternalTool' types */ + private Boolean newTab; /* only for 'ExternalTool' type */ + private CompletionRequirement completionRequirement; + private Boolean published; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getModuleId() { + return moduleId; + } + + public void setModuleId(Long moduleId) { + this.moduleId = moduleId; + } + + public Long getPosition() { + return position; + } + + public void setPosition(Long position) { + this.position = position; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Long getIndent() { + return indent; + } + + public void setIndent(Long indent) { + this.indent = indent; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public long getContentId() { + return contentId; + } + + public void setContenId(long contentId) { + this.contentId = contentId; + } + + public String getHtmlUrl() { + return htmlUrl; + } + + public void setHtmlUrl(String htmlUrl) { + this.htmlUrl = htmlUrl; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getPageUrl() { + return pageUrl; + } + + public void setPageUrl(String pageUrl) { + this.pageUrl = pageUrl; + } + + public String getExternalUrl() { + return externalUrl; + } + + public void setExternalUrl(String externalUrl) { + this.externalUrl = externalUrl; + } + + public java.lang.Boolean getNewTab() { + return newTab; + } + + public void setNewTab(java.lang.Boolean newTab) { + this.newTab = newTab; + } + + public CompletionRequirement getCompletionRequirement() { + return completionRequirement; + } + + public void setCompletionRequirement(CompletionRequirement completionRequirement) { + this.completionRequirement = completionRequirement; + } + + public Boolean getPublished() { + return published; + } + + public void setPublished(Boolean published) { + this.published = published; + } + + public class CompletionRequirement extends BaseCanvasModel implements Serializable { + private String type; + private java.math.BigDecimal minScore; + private Boolean completed; + + public java.math.BigDecimal getMinScore() { + return minScore; + } + + public void setMinScore(java.math.BigDecimal minScore) { + this.minScore = minScore; + } + + public Boolean getCompleted() { + return completed; + } + + public void setCompleted(Boolean completed) { + this.completed = completed; + } + + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + } +} \ No newline at end of file diff --git a/src/main/java/edu/ksu/canvas/requestOptions/ListModuleItemsOptions.java b/src/main/java/edu/ksu/canvas/requestOptions/ListModuleItemsOptions.java new file mode 100644 index 00000000..58dab3a3 --- /dev/null +++ b/src/main/java/edu/ksu/canvas/requestOptions/ListModuleItemsOptions.java @@ -0,0 +1,47 @@ +package edu.ksu.canvas.requestOptions; + +import java.util.List; + +/** + * Options for listing the items in a module. + * + * @see List module items + */ +public class ListModuleItemsOptions extends BaseOptions { + + public enum Include { + CONTENT_DETAILS + } + + private Long courseId; + + private Long moduleId; + + public ListModuleItemsOptions(Long courseId, Long moduleId) { + this.courseId = courseId; + this.moduleId = moduleId; + } + + public Long getCourseId() { + return courseId; + } + + public Long getModuleId() { + return moduleId; + } + + public ListModuleItemsOptions includes(List includes) { + addEnumList("include[]", includes); + return this; + } + + public ListModuleItemsOptions searchTerm(String searchTerm) { + addSingleItem("search_term", searchTerm); + return this; + } + + public ListModuleItemsOptions studentId(String studentId) { + addSingleItem("student_id", studentId); + return this; + } +} \ No newline at end of file diff --git a/src/test/java/edu/ksu/canvas/tests/module/ModuleItemListingTest.java b/src/test/java/edu/ksu/canvas/tests/module/ModuleItemListingTest.java new file mode 100644 index 00000000..7449bf4e --- /dev/null +++ b/src/test/java/edu/ksu/canvas/tests/module/ModuleItemListingTest.java @@ -0,0 +1,49 @@ +package edu.ksu.canvas.tests.module; + +import edu.ksu.canvas.CanvasTestBase; +import edu.ksu.canvas.impl.ModuleItemImpl; +import edu.ksu.canvas.interfaces.ModuleItemReader; +import edu.ksu.canvas.model.ModuleItem; +import edu.ksu.canvas.net.FakeRestClient; +import edu.ksu.canvas.requestOptions.ListModuleItemsOptions; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.IOException; +import java.util.List; + +public class ModuleItemListingTest extends CanvasTestBase { + @Autowired + private FakeRestClient fakeRestClient; + private ModuleItemReader moduleItemReader; + + @Before + public void setupData() { + moduleItemReader = new ModuleItemImpl(baseUrl, apiVersion, SOME_OAUTH_TOKEN, fakeRestClient, SOME_CONNECT_TIMEOUT, SOME_READ_TIMEOUT, DEFAULT_PAGINATION_PAGE_SIZE, false); + } + + @Test + public void testListModuleItemsInModule() throws IOException { + Long courseId = 1092L; + Long moduleId = 1059720L; + ListModuleItemsOptions options = new ListModuleItemsOptions(courseId, moduleId); + String url = baseUrl + "/api/v1/courses/1092/modules/1059720/items"; + + fakeRestClient.addSuccessResponse(url, "SampleJson/ModuleItems.json"); + List moduleItems = moduleItemReader.getModuleItemsInModule(options); + Assert.assertNotNull(moduleItems); + Assert.assertEquals(4, moduleItems.size()); + for (ModuleItem moduleItem: moduleItems) { /* Copied from https://github.com/instructure/CanvasAPI/blob/develop/src/test/java/ModuleItemUnitTest.java */ + Assert.assertTrue(moduleItem.getId() > 0); + Assert.assertNotNull(moduleItem.getType()); + Assert.assertNotNull(moduleItem.getTitle()); + Assert.assertNotNull(moduleItem.getHtmlUrl()); + Assert.assertNotNull(moduleItem.getUrl()); + if (moduleItem.getCompletionRequirement() != null) { + Assert.assertNotNull(moduleItem.getCompletionRequirement().getType()); + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/SampleJson/ModuleItems.json b/src/test/resources/SampleJson/ModuleItems.json new file mode 100644 index 00000000..d2088214 --- /dev/null +++ b/src/test/resources/SampleJson/ModuleItems.json @@ -0,0 +1,63 @@ +[ + { + "id": 9012239, + "indent": 0, + "position": 1, + "title": "Android 101", + "type": "Assignment", + "module_id": 1059720, + "html_url": "https://mobiledev.instructure.com/courses/833052/modules/items/9012239", + "content_id": 2241839, + "url": "https://mobiledev.instructure.com/api/v1/courses/833052/assignments/2241839", + "completion_requirement": { + "type": "must_submit", + "completed": true + } + }, + { + "id": 9012244, + "indent": 0, + "position": 2, + "title": "Favorite App Video", + "type": "Assignment", + "module_id": 1059720, + "html_url": "https://mobiledev.instructure.com/courses/833052/modules/items/9012244", + "content_id": 2241864, + "url": "https://mobiledev.instructure.com/api/v1/courses/833052/assignments/2241864", + "completion_requirement": { + "type": "min_score", + "min_score": "5", + "completed": true + } + }, + { + "id": 9012248, + "indent": 0, + "position": 3, + "title": "Android vs. iOS", + "type": "Discussion", + "module_id": 1059720, + "html_url": "https://mobiledev.instructure.com/courses/833052/modules/items/9012248", + "content_id": 1369942, + "url": "https://mobiledev.instructure.com/api/v1/courses/833052/discussion_topics/1369942", + "completion_requirement": { + "type": "must_contribute", + "completed": false + } + }, + { + "id": 9012251, + "indent": 0, + "position": 4, + "title": "Easy Quiz", + "type": "Quiz", + "module_id": 1059720, + "html_url": "https://mobiledev.instructure.com/courses/833052/modules/items/9012251", + "content_id": 757314, + "url": "https://mobiledev.instructure.com/api/v1/courses/833052/quizzes/757314", + "completion_requirement": { + "type": "must_submit", + "completed": true + } + } +] \ No newline at end of file